Replace Auth System #3
@@ -30,9 +30,9 @@ class AutoStats extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
PopularDaily::where('created_at', '<=', Carbon::now()->subMinutes(1440))->forceDelete();
|
PopularDaily::where('created_at', '<=', Carbon::now()->subMinutes(1440))->delete();
|
||||||
PopularWeekly::where('created_at', '<=', Carbon::now()->subMinutes(10080))->forceDelete();
|
PopularWeekly::where('created_at', '<=', Carbon::now()->subMinutes(10080))->delete();
|
||||||
PopularMonthly::where('created_at', '<=', Carbon::now()->subMinutes(43200))->forceDelete();
|
PopularMonthly::where('created_at', '<=', Carbon::now()->subMinutes(43200))->delete();
|
||||||
|
|
||||||
$this->comment('Automated Purge Stats Complete');
|
$this->comment('Automated Purge Stats Complete');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ class ResetUserDownloads extends Command
|
|||||||
|
|
||||||
// Clear old downloads which have expired
|
// Clear old downloads which have expired
|
||||||
UserDownload::where('created_at', '<=', Carbon::now()->subHour(6))
|
UserDownload::where('created_at', '<=', Carbon::now()->subHour(6))
|
||||||
->forceDelete();
|
->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class AlertController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function delete(int $alert_id): \Illuminate\Http\RedirectResponse
|
public function delete(int $alert_id): \Illuminate\Http\RedirectResponse
|
||||||
{
|
{
|
||||||
Alert::where('id', $alert_id)->forceDelete();
|
Alert::where('id', $alert_id)->delete();
|
||||||
|
|
||||||
cache()->forget('alerts');
|
cache()->forget('alerts');
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class SiteBackgroundController extends Controller
|
|||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
$bg = SiteBackground::where('id', $id)->firstOrFail();
|
$bg = SiteBackground::where('id', $id)->firstOrFail();
|
||||||
$bg->forceDelete();
|
$bg->delete();
|
||||||
|
|
||||||
$resolutions = [1440, 1080, 720, 640];
|
$resolutions = [1440, 1080, 720, 640];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class SubtitleController extends Controller
|
|||||||
|
|
||||||
// Clear everything
|
// Clear everything
|
||||||
foreach($episode->subtitles as $sub) {
|
foreach($episode->subtitles as $sub) {
|
||||||
$sub->forceDelete();
|
$sub->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $request->input('subtitles')) {
|
if (! $request->input('subtitles')) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Auth\LoginRequest;
|
use App\Http\Requests\Auth\LoginRequest;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -29,7 +28,7 @@ class AuthenticatedSessionController extends Controller
|
|||||||
|
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -36,6 +35,6 @@ class ConfirmablePasswordController extends Controller
|
|||||||
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
130
app/Http/Controllers/Auth/DiscordAuthController.php
Normal file
130
app/Http/Controllers/Auth/DiscordAuthController.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
|
||||||
|
class DiscordAuthController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Redirect to Discord
|
||||||
|
*/
|
||||||
|
public function redirect(): RedirectResponse
|
||||||
|
{
|
||||||
|
return Socialite::driver('discord')->redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback received from Discord
|
||||||
|
*/
|
||||||
|
public function callback(): RedirectResponse
|
||||||
|
{
|
||||||
|
$discordUser = Socialite::driver('discord')->user();
|
||||||
|
|
||||||
|
$user = User::where('discord_id', $discordUser->id)->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
// link by email if it already exists
|
||||||
|
$user = User::where('email', $discordUser->email)->first();
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
$user->update([
|
||||||
|
'discord_id' => $discordUser->id,
|
||||||
|
'discord_avatar' => $discordUser->avatar,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Create new user
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $discordUser->name,
|
||||||
|
'email' => $discordUser->email,
|
||||||
|
'discord_id' => $discordUser->id,
|
||||||
|
'discord_avatar' => $discordUser->avatar,
|
||||||
|
'password' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkDiscordAvatar($discordUser, $user);
|
||||||
|
$this->checkDiscordRoles($user);
|
||||||
|
|
||||||
|
Auth::login($user, true);
|
||||||
|
|
||||||
|
return redirect()->route('home.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if discord avatar changed
|
||||||
|
*/
|
||||||
|
private function checkDiscordAvatar(\Laravel\Socialite\Contracts\User $socialiteUser, User $user): void
|
||||||
|
{
|
||||||
|
if ($socialiteUser->avatar != $user->discord_avatar) {
|
||||||
|
$user->update(['discord_avatar' => $socialiteUser->avatar]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check Discord Roles if user is Patreon member
|
||||||
|
*/
|
||||||
|
private function checkDiscordRoles(User $user): void
|
||||||
|
{
|
||||||
|
// Should not ever happen
|
||||||
|
if (!$user->discord_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$guildId = config('discord.guild_id');
|
||||||
|
|
||||||
|
$response = Http::withToken(config('discord.discord_bot_token'), 'Bot')
|
||||||
|
->timeout(5)
|
||||||
|
->get("https://discord.com/api/v10/guilds/{$guildId}/members/{$user->discord_id}");
|
||||||
|
|
||||||
|
// User is not in the guild
|
||||||
|
if ($response->status() === 404) {
|
||||||
|
$user->update([
|
||||||
|
'is_patreon' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something else failed
|
||||||
|
if ($response->failed()) {
|
||||||
|
Log::warning('Discord role check failed', [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'discord_id' => $user->discord_id,
|
||||||
|
'status' => $response->status(),
|
||||||
|
'body' => $response->body(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$discordRoles = $response->json('roles', []);
|
||||||
|
$patreonRoles = config('discord.patreon_roles', []);
|
||||||
|
|
||||||
|
$isPatreon = false;
|
||||||
|
foreach($patreonRoles as $patreonRole)
|
||||||
|
{
|
||||||
|
if (in_array($patreonRole, $discordRoles, true)) {
|
||||||
|
$isPatreon = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update if something actually changed
|
||||||
|
if ($user->is_patreon !== $isPatreon) {
|
||||||
|
$user->update([
|
||||||
|
'is_patreon' => $isPatreon,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ class EmailVerificationNotificationController extends Controller
|
|||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->user()->sendEmailVerificationNotification();
|
$request->user()->sendEmailVerificationNotification();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
@@ -16,7 +15,7 @@ class EmailVerificationPromptController extends Controller
|
|||||||
public function __invoke(Request $request): RedirectResponse|View
|
public function __invoke(Request $request): RedirectResponse|View
|
||||||
{
|
{
|
||||||
return $request->user()->hasVerifiedEmail()
|
return $request->user()->hasVerifiedEmail()
|
||||||
? redirect()->intended(RouteServiceProvider::HOME)
|
? redirect()->intended(route('home.index', absolute: false))
|
||||||
: view('auth.verify-email');
|
: view('auth.verify-email');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Events\PasswordReset;
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -40,7 +41,7 @@ class NewPasswordController extends Controller
|
|||||||
// database. Otherwise we will parse the error and return the response.
|
// database. Otherwise we will parse the error and return the response.
|
||||||
$status = Password::reset(
|
$status = Password::reset(
|
||||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||||
function ($user) use ($request) {
|
function (User $user) use ($request) {
|
||||||
$user->forceFill([
|
$user->forceFill([
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
'remember_token' => Str::random(60),
|
'remember_token' => Str::random(60),
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ class PasswordController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request): RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
// If user logged in with Discord and has not yet a password, allow to set password
|
||||||
|
if ($request->user()->discord_id && is_null($request->user()->password))
|
||||||
|
{
|
||||||
|
$validated = $request->validateWithBag('updatePassword', [
|
||||||
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'password' => Hash::make($validated['password']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('status', 'password-updated');
|
||||||
|
}
|
||||||
|
|
||||||
$validated = $request->validateWithBag('updatePassword', [
|
$validated = $request->validateWithBag('updatePassword', [
|
||||||
'current_password' => ['required', 'current_password'],
|
'current_password' => ['required', 'current_password'],
|
||||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
|||||||
@@ -4,25 +4,15 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rules;
|
use Illuminate\Validation\Rules;
|
||||||
use Illuminate\View\View;
|
|
||||||
|
|
||||||
class RegisteredUserController extends Controller
|
class RegisteredUserController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Display the registration view.
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
return view('auth.register');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming registration request.
|
* Handle an incoming registration request.
|
||||||
*
|
*
|
||||||
@@ -32,7 +22,7 @@ class RegisteredUserController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -46,6 +36,6 @@ class RegisteredUserController extends Controller
|
|||||||
|
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
|
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Auth\Events\Verified;
|
use Illuminate\Auth\Events\Verified;
|
||||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -16,13 +15,13 @@ class VerifyEmailController extends Controller
|
|||||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
return redirect()->intended(route('home.index', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->user()->markEmailAsVerified()) {
|
if ($request->user()->markEmailAsVerified()) {
|
||||||
event(new Verified($request->user()));
|
event(new Verified($request->user()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
return redirect()->intended(route('home.index', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class NotificationController extends Controller
|
|||||||
->where('id', $request->input('id'))
|
->where('id', $request->input('id'))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
$notification->forceDelete();
|
$notification->delete();
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,13 +105,11 @@ class PlaylistController extends Controller
|
|||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$playlist = Playlist::where('user_id', $user->id)->where('id', $playlist_id)->firstOrFail();
|
$playlist = Playlist::where('user_id', $user->id)
|
||||||
|
->where('id', $playlist_id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
// Delete Playlist Episodes
|
$playlist->delete();
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlist->forceDelete();
|
|
||||||
|
|
||||||
return to_route('profile.playlists');
|
return to_route('profile.playlists');
|
||||||
}
|
}
|
||||||
@@ -128,8 +126,14 @@ class PlaylistController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$playlist = Playlist::where('user_id', $request->user()->id)->where('id', (int) $request->input('playlist'))->firstOrFail();
|
$playlist = Playlist::where('user_id', $request->user()->id)
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', (int) $request->input('episode'))->forceDelete();
|
->where('id', (int) $request->input('playlist'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
PlaylistEpisode::where('playlist_id', $playlist->id)
|
||||||
|
->where('episode_id', (int) $request->input('episode'))
|
||||||
|
->delete();
|
||||||
|
|
||||||
$this->playlistService->reorderPositions($playlist);
|
$this->playlistService->reorderPositions($playlist);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
use App\Models\Playlist;
|
||||||
|
use App\Models\PlaylistEpisode;
|
||||||
|
use App\Models\User;
|
||||||
use App\Http\Requests\ProfileUpdateRequest;
|
use App\Http\Requests\ProfileUpdateRequest;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
use Conner\Tagging\Model\Tag;
|
use Conner\Tagging\Model\Tag;
|
||||||
|
|
||||||
@@ -17,7 +25,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user page.
|
* Display the user page.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request): \Illuminate\View\View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.index', [
|
return view('profile.index', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -27,7 +35,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's settings form.
|
* Display the user's settings form.
|
||||||
*/
|
*/
|
||||||
public function settings(Request $request): \Illuminate\View\View
|
public function settings(Request $request): View
|
||||||
{
|
{
|
||||||
$example = Episode::where('title', 'Succubus Yondara Gibo ga Kita!?')->first();
|
$example = Episode::where('title', 'Succubus Yondara Gibo ga Kita!?')->first();
|
||||||
|
|
||||||
@@ -37,10 +45,33 @@ class ProfileController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's profile information.
|
||||||
|
*/
|
||||||
|
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// Fill everything except the image
|
||||||
|
$user->fill($request->safe()->except('image'));
|
||||||
|
|
||||||
|
if ($request->user()->isDirty('email')) {
|
||||||
|
$request->user()->email_verified_at = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$this->storeAvatar($request->file('image'), $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return Redirect::route('profile.settings')->with('status', 'profile-updated');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the user's watched page.
|
* Display the user's watched page.
|
||||||
*/
|
*/
|
||||||
public function watched(Request $request): \Illuminate\View\View
|
public function watched(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.watched', [
|
return view('profile.watched', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -50,7 +81,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's comments page.
|
* Display the user's comments page.
|
||||||
*/
|
*/
|
||||||
public function comments(Request $request): \Illuminate\View\View
|
public function comments(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.comments', [
|
return view('profile.comments', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -60,7 +91,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's likes page.
|
* Display the user's likes page.
|
||||||
*/
|
*/
|
||||||
public function likes(Request $request): \Illuminate\View\View
|
public function likes(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.likes', [
|
return view('profile.likes', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -70,7 +101,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update user settings.
|
* Update user settings.
|
||||||
*/
|
*/
|
||||||
public function saveSettings(Request $request): \Illuminate\Http\RedirectResponse
|
public function saveSettings(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$user->search_design = $request->input('searchDesign') == 'thumbnail';
|
$user->search_design = $request->input('searchDesign') == 'thumbnail';
|
||||||
@@ -84,7 +115,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update user tag blacklist.
|
* Update user tag blacklist.
|
||||||
*/
|
*/
|
||||||
public function saveBlacklist(Request $request): \Illuminate\Http\RedirectResponse
|
public function saveBlacklist(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$tags = json_decode($request->input('tags'));
|
$tags = json_decode($request->input('tags'));
|
||||||
@@ -112,19 +143,60 @@ class ProfileController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function destroy(Request $request): \Illuminate\Http\RedirectResponse
|
public function destroy(Request $request): \Illuminate\Http\RedirectResponse
|
||||||
{
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// Verify password if user has password
|
||||||
|
if (!is_null($user->password)) {
|
||||||
$request->validateWithBag('userDeletion', [
|
$request->validateWithBag('userDeletion', [
|
||||||
'password' => ['required', 'current_password'],
|
'password' => ['required', 'current_password'],
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
// Update comments to deleted user
|
||||||
|
DB::table('comments')->where('commenter_id', '=', $user->id)->update(['commenter_id' => 1]);
|
||||||
|
|
||||||
|
// Delete Profile Picture
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete($user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
|
|
||||||
$user->delete();
|
$user->delete();
|
||||||
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
cache()->flush();
|
||||||
|
|
||||||
return Redirect::to('/');
|
return Redirect::to('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store custom user avatar.
|
||||||
|
*/
|
||||||
|
protected function storeAvatar(\Illuminate\Http\UploadedFile $file, User $user): void
|
||||||
|
{
|
||||||
|
// Create Folder for Image Upload
|
||||||
|
if (! Storage::disk('public')->exists("/images/avatars")) {
|
||||||
|
Storage::disk('public')->makeDirectory("/images/avatars");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old avatar if it exists
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete($user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = "images/avatars/{$user->id}.webp";
|
||||||
|
|
||||||
|
$image = Image::read($file->getRealPath())
|
||||||
|
->cover(128, 128)
|
||||||
|
->toWebp(quality: 85);
|
||||||
|
|
||||||
|
Storage::disk('public')->put($filename, $image);
|
||||||
|
|
||||||
|
$user->avatar = $filename;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Playlist;
|
|
||||||
use App\Models\PlaylistEpisode;
|
|
||||||
use App\Models\Watched;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class UserController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Display User Page.
|
|
||||||
*/
|
|
||||||
public function index(string $username): \Illuminate\View\View
|
|
||||||
{
|
|
||||||
$user = User::where('username', $username)
|
|
||||||
->select('id', 'username', 'global_name', 'avatar', 'created_at', 'is_patreon')
|
|
||||||
->firstOrFail();
|
|
||||||
|
|
||||||
return view('user.index', [
|
|
||||||
'user' => $user,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete User.
|
|
||||||
*/
|
|
||||||
public function delete(Request $request): \Illuminate\Http\RedirectResponse
|
|
||||||
{
|
|
||||||
$user = User::where('id', $request->user()->id)->firstOrFail();
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlists = Playlist::where('user_id', $user->id)->get();
|
|
||||||
foreach($playlists as $playlist) {
|
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
$playlist->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update comments to deleted user
|
|
||||||
DB::table('comments')->where('commenter_id', '=', $user->id)->update(['commenter_id' => 1]);
|
|
||||||
|
|
||||||
$user->forceDelete();
|
|
||||||
|
|
||||||
Auth::guard('web')->logout();
|
|
||||||
|
|
||||||
$request->session()->invalidate();
|
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
|
||||||
|
|
||||||
cache()->flush();
|
|
||||||
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ class LoginRequest extends FormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
@@ -80,6 +80,6 @@ class LoginRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function throttleKey(): string
|
public function throttleKey(): string
|
||||||
{
|
{
|
||||||
return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip());
|
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,26 @@ class ProfileUpdateRequest extends FormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
'image' => [
|
||||||
|
'nullable',
|
||||||
|
'image',
|
||||||
|
'mimes:jpg,png,jpeg,webp,gif',
|
||||||
|
'max:8192'
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'lowercase',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
Rule::unique(User::class)->ignore($this->user()->id),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ class AdminCommentSearch extends Component
|
|||||||
{
|
{
|
||||||
$comments = DB::table('comments')
|
$comments = DB::table('comments')
|
||||||
->join('users', 'comments.commenter_id', '=', 'users.id')
|
->join('users', 'comments.commenter_id', '=', 'users.id')
|
||||||
->select('comments.*', 'users.username')
|
->select('comments.*', 'users.name')
|
||||||
->when($this->search !== '', fn ($query) => $query->where('comment', 'LIKE', "%$this->search%"))
|
->when($this->search !== '', fn ($query) => $query->where('comment', 'LIKE', "%$this->search%"))
|
||||||
->when($this->userSearch !== '', fn ($query) => $query->where('username', 'LIKE', "%$this->userSearch%"))
|
->when($this->userSearch !== '', fn ($query) => $query->where('name', 'LIKE', "%$this->userSearch%"))
|
||||||
->paginate(12);
|
->paginate(12);
|
||||||
|
|
||||||
return view('livewire.admin-comment-search', [
|
return view('livewire.admin-comment-search', [
|
||||||
|
|||||||
@@ -43,10 +43,7 @@ class AdminUserSearch extends Component
|
|||||||
$users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000))
|
$users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000))
|
||||||
->when($this->patreon !== [], fn ($query) => $query->where('is_patreon', 1))
|
->when($this->patreon !== [], fn ($query) => $query->where('is_patreon', 1))
|
||||||
->when($this->banned !== [], fn ($query) => $query->where('is_banned', 1))
|
->when($this->banned !== [], fn ($query) => $query->where('is_banned', 1))
|
||||||
->when($this->search !== '', fn ($query) => $query->where(function($query) {
|
->when($this->search !== '', fn ($query) => $query->where('name', 'like', '%'.$this->search.'%'))
|
||||||
$query->where('username', 'like', '%'.$this->search.'%')
|
|
||||||
->orWhere('global_name', 'like', '%'.$this->search.'%');
|
|
||||||
}))
|
|
||||||
->paginate(20);
|
->paginate(20);
|
||||||
|
|
||||||
return view('livewire.admin-user-search', [
|
return view('livewire.admin-user-search', [
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Livewire\Component;
|
|||||||
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class DownloadsFree extends Component
|
class DownloadsFree extends Component
|
||||||
{
|
{
|
||||||
@@ -51,7 +50,7 @@ class DownloadsFree extends Component
|
|||||||
// Check timestamp
|
// Check timestamp
|
||||||
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
|
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
|
||||||
// Already expired
|
// Already expired
|
||||||
$alreadyDownloaded->forceDelete();
|
$alreadyDownloaded->delete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
//use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
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\Storage;
|
||||||
|
|
||||||
use Jakyeru\Larascord\Traits\InteractsWithDiscord;
|
|
||||||
use Laravelista\Comments\Commenter;
|
use Laravelista\Comments\Commenter;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
use HasApiTokens, HasFactory, Notifiable, InteractsWithDiscord, Commenter;
|
use HasFactory, Notifiable, Commenter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -23,22 +23,14 @@ class User extends Authenticatable
|
|||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'id',
|
'name',
|
||||||
'username',
|
|
||||||
'global_name',
|
|
||||||
'discriminator',
|
|
||||||
'email',
|
'email',
|
||||||
'avatar',
|
'password',
|
||||||
'verified',
|
|
||||||
'banner',
|
|
||||||
'banner_color',
|
|
||||||
'accent_color',
|
|
||||||
'locale',
|
'locale',
|
||||||
'mfa_enabled',
|
|
||||||
'premium_type',
|
|
||||||
'public_flags',
|
|
||||||
'roles',
|
|
||||||
'is_banned',
|
'is_banned',
|
||||||
|
// Discord
|
||||||
|
'discord_id',
|
||||||
|
'discord_avatar',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +39,7 @@ class User extends Authenticatable
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -56,24 +49,21 @@ class User extends Authenticatable
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
// Laravel defaults
|
||||||
'username' => 'string',
|
'email_verified_at' => 'datetime',
|
||||||
'global_name' => 'string',
|
'password' => 'hashed',
|
||||||
'discriminator' => 'string',
|
// Other
|
||||||
|
'name' => 'string',
|
||||||
'email' => 'string',
|
'email' => 'string',
|
||||||
'avatar' => 'string',
|
|
||||||
'verified' => 'boolean',
|
|
||||||
'banner' => 'string',
|
|
||||||
'banner_color' => 'string',
|
|
||||||
'accent_color' => 'string',
|
|
||||||
'locale' => 'string',
|
'locale' => 'string',
|
||||||
'mfa_enabled' => 'boolean',
|
|
||||||
'premium_type' => 'integer',
|
|
||||||
'public_flags' => 'integer',
|
|
||||||
'roles' => 'json',
|
'roles' => 'json',
|
||||||
'tag_blacklist' => 'array',
|
'tag_blacklist' => 'array',
|
||||||
|
// Discord
|
||||||
|
'discord_id' => 'integer',
|
||||||
|
'discord_avatar' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has Many Playlists.
|
* Has Many Playlists.
|
||||||
*/
|
*/
|
||||||
@@ -105,4 +95,22 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return DB::table('comments')->where('commenter_id', $this->id)->count();
|
return DB::table('comments')->where('commenter_id', $this->id)->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user avatar image url.
|
||||||
|
*/
|
||||||
|
public function getAvatar(): string
|
||||||
|
{
|
||||||
|
if ($this->discord_id && $this->discord_avatar && !$this->avatar)
|
||||||
|
{
|
||||||
|
return "https://external-content.duckduckgo.com/iu/?u={$this->discord_avatar}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->avatar)
|
||||||
|
{
|
||||||
|
return Storage::url($this->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return asset('images/default-avatar.webp');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class CommentService
|
|||||||
if (Config::get('comments.soft_deletes') == true) {
|
if (Config::get('comments.soft_deletes') == true) {
|
||||||
$comment->delete();
|
$comment->delete();
|
||||||
} else {
|
} else {
|
||||||
$comment->forceDelete();
|
$comment->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,12 +122,11 @@ class CommentService
|
|||||||
$url = '/hentai/' . $episode->slug . '#comment-' . $reply->id;
|
$url = '/hentai/' . $episode->slug . '#comment-' . $reply->id;
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$username = $user->global_name ?? $user->username;
|
|
||||||
|
|
||||||
$parentCommentUser = User::where('id', $comment->commenter_id)->firstOrFail();
|
$parentCommentUser = User::where('id', $comment->commenter_id)->firstOrFail();
|
||||||
$parentCommentUser->notify(
|
$parentCommentUser->notify(
|
||||||
new CommentNotification(
|
new CommentNotification(
|
||||||
"{$username} replied to your comment.",
|
"{$user->name} replied to your comment.",
|
||||||
Str::limit($reply->comment, 50),
|
Str::limit($reply->comment, 50),
|
||||||
$url
|
$url
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Jakyeru\Larascord\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Jakyeru\Larascord\Http\Requests\StoreUserRequest;
|
|
||||||
use Jakyeru\Larascord\Services\DiscordService;
|
|
||||||
|
|
||||||
use RealRashid\SweetAlert\Facades\Alert;
|
|
||||||
|
|
||||||
class DiscordController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handles the Discord OAuth2 login.
|
|
||||||
*/
|
|
||||||
public function handle(StoreUserRequest $request): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
// Making sure the "guilds" scope was added to .env if there are any guilds specified in "larascord.guilds".
|
|
||||||
if (count(config('larascord.guilds'))) {
|
|
||||||
if (!in_array('guilds', explode('&', config('larascord.scopes')))) {
|
|
||||||
return $this->throwError('missing_guilds_scope');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting the accessToken from the Discord API.
|
|
||||||
try {
|
|
||||||
$accessToken = (new DiscordService())->getAccessTokenFromCode($request->get('code'));
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('invalid_code', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the user from the Discord API.
|
|
||||||
try {
|
|
||||||
$user = (new DiscordService())->getCurrentUser($accessToken);
|
|
||||||
$user->setAccessToken($accessToken);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('authorization_failed', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making sure the user has an email if the email scope is set.
|
|
||||||
if (in_array('email', explode('&', config('larascord.scopes')))) {
|
|
||||||
if (empty($user->email)) {
|
|
||||||
return $this->throwError('missing_email');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth()->check()) {
|
|
||||||
// Making sure the current logged-in user's ID is matching the ID retrieved from the Discord API.
|
|
||||||
if (auth()->id() !== (int)$user->id) {
|
|
||||||
auth()->logout();
|
|
||||||
return $this->throwError('invalid_user');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirming the session in case the user was redirected from the password.confirm middleware.
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trying to create or update the user in the database.
|
|
||||||
// Initiating a database transaction in case something goes wrong.
|
|
||||||
DB::beginTransaction();
|
|
||||||
try {
|
|
||||||
$user = (new DiscordService())->createOrUpdateUser($user);
|
|
||||||
$user->accessToken()->updateOrCreate([], $accessToken->toArray());
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('database_error', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verifying if the user is soft-deleted.
|
|
||||||
if (Schema::hasColumn('users', 'deleted_at')) {
|
|
||||||
if ($user->trashed()) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('user_deleted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patreon check
|
|
||||||
try {
|
|
||||||
if (!$accessToken->hasScopes(['guilds', 'guilds.members.read'])) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('missing_guilds_members_read_scope');
|
|
||||||
}
|
|
||||||
$guildMember = (new DiscordService())->getGuildMember($accessToken, config('discord.guild_id'));
|
|
||||||
$patreonroles = config('discord.patreon_roles');
|
|
||||||
$user->is_patreon = false;
|
|
||||||
if ((new DiscordService())->hasRoleInGuild($guildMember, $patreonroles)) {
|
|
||||||
$user->is_patreon = true;
|
|
||||||
}
|
|
||||||
$user->save();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Clearly not a patreon
|
|
||||||
$user->is_patreon = false;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Committing the database transaction.
|
|
||||||
DB::commit();
|
|
||||||
|
|
||||||
// Authenticating the user if the user is not logged in.
|
|
||||||
if (!auth()->check()) {
|
|
||||||
auth()->login($user, config('larascord.remember_me', false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirecting the user to the intended page or to the home page.
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the throwing of an error.
|
|
||||||
*/
|
|
||||||
private function throwError(string $message, \Exception $exception = NULL): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
if (app()->hasDebugModeEnabled()) {
|
|
||||||
return response()->json([
|
|
||||||
'larascord_message' => config('larascord.error_messages.' . $message),
|
|
||||||
'message' => $exception?->getMessage(),
|
|
||||||
'code' => $exception?->getCode()
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
if (config('larascord.error_messages.' . $message . '.redirect')) {
|
|
||||||
Alert::error('Error', config('larascord.error_messages.' . $message . '.message', 'An error occurred while trying to log you in.'));
|
|
||||||
return redirect(config('larascord.error_messages.' . $message . '.redirect'))->with('error', config('larascord.error_messages.' . $message . '.message', 'An error occurred while trying to log you in.'));
|
|
||||||
} else {
|
|
||||||
return redirect('/')->with('error', config('larascord.error_messages.' . $message, 'An error occurred while trying to log you in.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the deletion of the user.
|
|
||||||
*/
|
|
||||||
public function destroy(): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
// Revoking the OAuth2 access token.
|
|
||||||
try {
|
|
||||||
(new DiscordService())->revokeAccessToken(auth()->user()->accessToken()->first()->refresh_token);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('revoke_token_failed', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleting the user from the database.
|
|
||||||
auth()->user()->delete();
|
|
||||||
|
|
||||||
// Showing the success message.
|
|
||||||
if (config('larascord.success_messages.user_deleted.redirect')) {
|
|
||||||
return redirect(config('larascord.success_messages.user_deleted.redirect'))->with('success', config('larascord.success_messages.user_deleted.message', 'Your account has been deleted.'));
|
|
||||||
} else {
|
|
||||||
return redirect('/')->with('success', config('larascord.success_messages.user_deleted', 'Your account has been deleted.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Jakyeru\Larascord\Services;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\OldUser;
|
|
||||||
use App\Models\Playlist;
|
|
||||||
use App\Models\PlaylistEpisode;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Http\Client\RequestException;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Jakyeru\Larascord\Types\AccessToken;
|
|
||||||
use Jakyeru\Larascord\Types\GuildMember;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class DiscordService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The Discord OAuth2 token URL.
|
|
||||||
*/
|
|
||||||
protected string $tokenURL = "https://discord.com/api/oauth2/token";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Discord API base URL.
|
|
||||||
*/
|
|
||||||
protected string $baseApi = "https://discord.com/api";
|
|
||||||
/**
|
|
||||||
* The required data for the token request.
|
|
||||||
*/
|
|
||||||
protected array $tokenData = [
|
|
||||||
"client_id" => NULL,
|
|
||||||
"client_secret" => NULL,
|
|
||||||
"grant_type" => "authorization_code",
|
|
||||||
"code" => NULL,
|
|
||||||
"redirect_uri" => NULL,
|
|
||||||
"scope" => null
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UserService constructor.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->tokenData['client_id'] = config('larascord.client_id');
|
|
||||||
$this->tokenData['client_secret'] = config('larascord.client_secret');
|
|
||||||
$this->tokenData['grant_type'] = config('larascord.grant_type');
|
|
||||||
$this->tokenData['redirect_uri'] = config('larascord.redirect_uri');
|
|
||||||
$this->tokenData['scope'] = config('larascord.scopes');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the Discord OAuth2 callback and returns the access token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function getAccessTokenFromCode(string $code): AccessToken
|
|
||||||
{
|
|
||||||
$this->tokenData['code'] = $code;
|
|
||||||
|
|
||||||
$response = Http::asForm()->post($this->tokenURL, $this->tokenData);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new AccessToken(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get access token from refresh token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function refreshAccessToken(string $refreshToken): AccessToken
|
|
||||||
{
|
|
||||||
$response = Http::asForm()->post($this->tokenURL, [
|
|
||||||
'client_id' => config('larascord.client_id'),
|
|
||||||
'client_secret' => config('larascord.client_secret'),
|
|
||||||
'grant_type' => 'refresh_token',
|
|
||||||
'refresh_token' => $refreshToken,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new AccessToken(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticates the user with the access token and returns the user data.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function getCurrentUser(AccessToken $accessToken): \Jakyeru\Larascord\Types\User
|
|
||||||
{
|
|
||||||
$response = Http::withToken($accessToken->access_token)->get($this->baseApi . '/users/@me');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new \Jakyeru\Larascord\Types\User(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the user's guilds.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getCurrentUserGuilds(AccessToken $accessToken, bool $withCounts = false): array
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScope('guilds')) throw new Exception(config('larascord.error_messages.missing_guilds_scope.message'));
|
|
||||||
|
|
||||||
$endpoint = '/users/@me/guilds';
|
|
||||||
|
|
||||||
if ($withCounts) {
|
|
||||||
$endpoint .= '?with_counts=true';
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . $endpoint);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return array_map(function ($guild) {
|
|
||||||
return new \Jakyeru\Larascord\Types\Guild($guild);
|
|
||||||
}, json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the Guild Member object for a user.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getGuildMember(AccessToken $accessToken, string $guildId): GuildMember
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScopes(['guilds', 'guilds.members.read'])) throw new Exception(config('larascord.error_messages.missing_guilds_members_read_scope.message'));
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . '/users/@me/guilds/' . $guildId . '/member');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new GuildMember(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the User's connections.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getCurrentUserConnections(AccessToken $accessToken): array
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScope('connections')) throw new Exception('The "connections" scope is required.');
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . '/users/@me/connections');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return array_map(function ($connection) {
|
|
||||||
return new \Jakyeru\Larascord\Types\Connection($connection);
|
|
||||||
}, json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join a guild.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function joinGuild(AccessToken $accessToken, User $user, string $guildId, array $options = []): GuildMember
|
|
||||||
{
|
|
||||||
if (!config('larascord.access_token')) throw new Exception(config('larascord.error_messages.missing_access_token.message'));
|
|
||||||
if (!$accessToken->hasScope('guilds.join')) throw new Exception('The "guilds" and "guilds.join" scopes are required.');
|
|
||||||
|
|
||||||
$response = Http::withToken(config('larascord.access_token'), 'Bot')->put($this->baseApi . '/guilds/' . $guildId . '/members/' . $user->id, array_merge([
|
|
||||||
'access_token' => $accessToken->access_token,
|
|
||||||
], $options));
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
if ($response->status() === 204) return throw new Exception('User is already in the guild.');
|
|
||||||
|
|
||||||
return new GuildMember(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create or update a user in the database.
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function createOrUpdateUser(\Jakyeru\Larascord\Types\User $user): User
|
|
||||||
{
|
|
||||||
if (!$user->getAccessToken()) {
|
|
||||||
throw new Exception('User access token is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$forgottenUser = User::where('email', '=', $user->email)->where('id', '!=', $user->id)->first();
|
|
||||||
if ($forgottenUser) {
|
|
||||||
// This case should never happen (TM) - The discord id changed
|
|
||||||
// The user probably re-created their discord account with the same email
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlists = Playlist::where('user_id', $forgottenUser->id)->get();
|
|
||||||
foreach($playlists as $playlist) {
|
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
$playlist->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update comments to deleted user
|
|
||||||
DB::table('comments')->where('commenter_id', '=', $forgottenUser->id)->update(['commenter_id' => 1]);
|
|
||||||
|
|
||||||
$forgottenUser->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return User::updateOrCreate(
|
|
||||||
[
|
|
||||||
'id' => $user->id,
|
|
||||||
],
|
|
||||||
$user->toArray(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if the user is in the specified guild(s).
|
|
||||||
*/
|
|
||||||
public function isUserInGuilds(array $guilds): bool
|
|
||||||
{
|
|
||||||
// Verify if the user is in all the specified guilds if strict mode is enabled.
|
|
||||||
if (config('larascord.guilds_strict')) {
|
|
||||||
return empty(array_diff(config('larascord.guilds'), array_column($guilds, 'id')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify if the user is in any of the specified guilds if strict mode is disabled.
|
|
||||||
return !empty(array_intersect(config('larascord.guilds'), array_column($guilds, 'id')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if the user has the specified role(s) in the specified guild.
|
|
||||||
*/
|
|
||||||
public function hasRoleInGuild(GuildMember $guildMember, array $roles): bool
|
|
||||||
{
|
|
||||||
// Verify if the user has any of the specified roles.
|
|
||||||
return !empty(array_intersect($roles, $guildMember->roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the user's roles in the database.
|
|
||||||
*/
|
|
||||||
public function updateUserRoles(User $user, GuildMember $guildMember, int $guildId): void
|
|
||||||
{
|
|
||||||
// Updating the user's roles in the database.
|
|
||||||
$updatedRoles = $user->roles;
|
|
||||||
$updatedRoles[$guildId] = $guildMember->roles;
|
|
||||||
$user->roles = $updatedRoles;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revoke the user's access token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function revokeAccessToken(string $accessToken): object
|
|
||||||
{
|
|
||||||
$response = Http::asForm()->post($this->tokenURL . '/revoke', [
|
|
||||||
'token' => $accessToken,
|
|
||||||
'client_id' => config('larascord.client_id'),
|
|
||||||
'client_secret' => config('larascord.client_secret'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return json_decode($response->body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -19,6 +20,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
|
||||||
|
$event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class GalleryService
|
|||||||
foreach ($oldGallery as $oldImage) {
|
foreach ($oldGallery as $oldImage) {
|
||||||
Storage::disk('public')->delete($oldImage->image_url);
|
Storage::disk('public')->delete($oldImage->image_url);
|
||||||
Storage::disk('public')->delete($oldImage->thumbnail_url);
|
Storage::disk('public')->delete($oldImage->thumbnail_url);
|
||||||
$oldImage->forceDelete();
|
$oldImage->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
"http-interop/http-factory-guzzle": "^1.2",
|
"http-interop/http-factory-guzzle": "^1.2",
|
||||||
"intervention/image": "^3.9",
|
"intervention/image": "^3.9",
|
||||||
"intervention/image-laravel": "^1.3",
|
"intervention/image-laravel": "^1.3",
|
||||||
"jakyeru/larascord": "^6.0",
|
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/scout": "^10.20",
|
"laravel/scout": "^10.20",
|
||||||
|
"laravel/socialite": "^5.24",
|
||||||
"laravel/tinker": "^2.10",
|
"laravel/tinker": "^2.10",
|
||||||
"laravelista/comments": "dev-l11-compatibility",
|
"laravelista/comments": "dev-l11-compatibility",
|
||||||
"livewire/livewire": "^3.6.4",
|
"livewire/livewire": "^3.6.4",
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"predis/predis": "^2.2",
|
"predis/predis": "^2.2",
|
||||||
"realrashid/sweet-alert": "^7.2",
|
"realrashid/sweet-alert": "^7.2",
|
||||||
"rtconner/laravel-tagging": "^4.1",
|
"rtconner/laravel-tagging": "^4.1",
|
||||||
|
"socialiteproviders/discord": "^4.2",
|
||||||
"spatie/laravel-discord-alerts": "^1.5",
|
"spatie/laravel-discord-alerts": "^1.5",
|
||||||
"spatie/laravel-sitemap": "^7.3",
|
"spatie/laravel-sitemap": "^7.3",
|
||||||
"vluzrmos/language-detector": "^2.3"
|
"vluzrmos/language-detector": "^2.3"
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-debugbar": "^3.14.7",
|
"barryvdh/laravel-debugbar": "^3.14.7",
|
||||||
"fakerphp/faker": "^1.24.0",
|
"fakerphp/faker": "^1.24.0",
|
||||||
|
"laravel/breeze": "^2.3",
|
||||||
"laravel/pint": "^1.18",
|
"laravel/pint": "^1.18",
|
||||||
"laravel/sail": "^1.38",
|
"laravel/sail": "^1.38",
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.4.4",
|
||||||
@@ -49,8 +51,6 @@
|
|||||||
],
|
],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"exclude-from-classmap": [
|
"exclude-from-classmap": [
|
||||||
"vendor/jakyeru/larascord/src/Http/Services/DiscordService.php",
|
|
||||||
"vendor/jakyeru/larascord/src/Http/Controllers/DiscordController.php",
|
|
||||||
"vendor/laravelista/comments/src/CommentPolicy.php",
|
"vendor/laravelista/comments/src/CommentPolicy.php",
|
||||||
"vendor/laravelista/comments/src/CommentService.php"
|
"vendor/laravelista/comments/src/CommentService.php"
|
||||||
],
|
],
|
||||||
@@ -60,8 +60,6 @@
|
|||||||
"Database\\Seeders\\": "database/seeders/"
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"app/Override/Discord/Services/DiscordService.php",
|
|
||||||
"app/Override/Discord/DiscordController.php",
|
|
||||||
"app/Override/Comments/CommentPolicy.php",
|
"app/Override/Comments/CommentPolicy.php",
|
||||||
"app/Override/Comments/CommentService.php"
|
"app/Override/Comments/CommentService.php"
|
||||||
]
|
]
|
||||||
|
|||||||
741
composer.lock
generated
741
composer.lock
generated
@@ -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": "484d21a7c10b1609a22d642e71a71cc3",
|
"content-hash": "749225dc4ea2aca06f1639bef889cc59",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -631,6 +631,69 @@
|
|||||||
},
|
},
|
||||||
"time": "2019-12-30T22:54:17+00:00"
|
"time": "2019-12-30T22:54:17+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase/php-jwt",
|
||||||
|
"version": "v7.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/firebase/php-jwt.git",
|
||||||
|
"reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65",
|
||||||
|
"reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^7.4",
|
||||||
|
"phpspec/prophecy-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"psr/cache": "^2.0||^3.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||||
|
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Firebase\\JWT\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Neuman Vong",
|
||||||
|
"email": "neuman+pear@twilio.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anant Narayanan",
|
||||||
|
"email": "anant@php.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||||
|
"homepage": "https://github.com/firebase/php-jwt",
|
||||||
|
"keywords": [
|
||||||
|
"jwt",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||||
|
"source": "https://github.com/firebase/php-jwt/tree/v7.0.2"
|
||||||
|
},
|
||||||
|
"time": "2025-12-16T22:17:28+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fruitcake/php-cors",
|
"name": "fruitcake/php-cors",
|
||||||
"version": "v1.3.0",
|
"version": "v1.3.0",
|
||||||
@@ -1533,59 +1596,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-04-04T15:09:55+00:00"
|
"time": "2025-04-04T15:09:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "jakyeru/larascord",
|
|
||||||
"version": "v6.0.3",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/JakyeRU/Larascord.git",
|
|
||||||
"reference": "d1099d1418022eda970fec4f13634ee5fbee93b3"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/JakyeRU/Larascord/zipball/d1099d1418022eda970fec4f13634ee5fbee93b3",
|
|
||||||
"reference": "d1099d1418022eda970fec4f13634ee5fbee93b3",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"guzzlehttp/guzzle": "^7.5",
|
|
||||||
"laravel/breeze": "^v2.0",
|
|
||||||
"laravel/framework": "^11",
|
|
||||||
"php": "^8.2|^8.3"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"orchestra/testbench": "^9"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"laravel": {
|
|
||||||
"providers": [
|
|
||||||
"Jakyeru\\Larascord\\LarascordServiceProvider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Jakyeru\\Larascord\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jakye",
|
|
||||||
"email": "jakyeru@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Larascord is a package that allows you to authenticate users in your Laravel application using Discord.",
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/JakyeRU/Larascord/issues",
|
|
||||||
"source": "https://github.com/JakyeRU/Larascord/tree/v6.0.3"
|
|
||||||
},
|
|
||||||
"time": "2025-06-18T18:41:42+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "jaybizzle/crawler-detect",
|
"name": "jaybizzle/crawler-detect",
|
||||||
"version": "v1.3.6",
|
"version": "v1.3.6",
|
||||||
@@ -1638,67 +1648,6 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-09-30T16:22:43+00:00"
|
"time": "2025-09-30T16:22:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "laravel/breeze",
|
|
||||||
"version": "v2.3.8",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/laravel/breeze.git",
|
|
||||||
"reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/laravel/breeze/zipball/1a29c5792818bd4cddf70b5f743a227e02fbcfcd",
|
|
||||||
"reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"illuminate/console": "^11.0|^12.0",
|
|
||||||
"illuminate/filesystem": "^11.0|^12.0",
|
|
||||||
"illuminate/support": "^11.0|^12.0",
|
|
||||||
"illuminate/validation": "^11.0|^12.0",
|
|
||||||
"php": "^8.2.0",
|
|
||||||
"symfony/console": "^7.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"laravel/framework": "^11.0|^12.0",
|
|
||||||
"orchestra/testbench-core": "^9.0|^10.0",
|
|
||||||
"phpstan/phpstan": "^2.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"laravel": {
|
|
||||||
"providers": [
|
|
||||||
"Laravel\\Breeze\\BreezeServiceProvider"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Laravel\\Breeze\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Taylor Otwell",
|
|
||||||
"email": "taylor@laravel.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.",
|
|
||||||
"keywords": [
|
|
||||||
"auth",
|
|
||||||
"laravel"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/laravel/breeze/issues",
|
|
||||||
"source": "https://github.com/laravel/breeze"
|
|
||||||
},
|
|
||||||
"time": "2025-07-18T18:49:59+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v11.46.1",
|
"version": "v11.46.1",
|
||||||
@@ -2179,6 +2128,78 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-09-22T17:29:40+00:00"
|
"time": "2025-09-22T17:29:40+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/socialite",
|
||||||
|
"version": "v5.24.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/socialite.git",
|
||||||
|
"reference": "25e28c14d55404886777af1d77cf030e0f633142"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/socialite/zipball/25e28c14d55404886777af1d77cf030e0f633142",
|
||||||
|
"reference": "25e28c14d55404886777af1d77cf030e0f633142",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"firebase/php-jwt": "^6.4|^7.0",
|
||||||
|
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||||
|
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"league/oauth1-client": "^1.11",
|
||||||
|
"php": "^7.2|^8.0",
|
||||||
|
"phpseclib/phpseclib": "^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8",
|
||||||
|
"phpstan/phpstan": "^1.12.23",
|
||||||
|
"phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"aliases": {
|
||||||
|
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Socialite\\SocialiteServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Socialite\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
|
||||||
|
"homepage": "https://laravel.com",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"oauth"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/socialite/issues",
|
||||||
|
"source": "https://github.com/laravel/socialite"
|
||||||
|
},
|
||||||
|
"time": "2026-01-01T02:57:21+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/tinker",
|
"name": "laravel/tinker",
|
||||||
"version": "v2.10.1",
|
"version": "v2.10.1",
|
||||||
@@ -2686,6 +2707,82 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-09-21T08:32:55+00:00"
|
"time": "2024-09-21T08:32:55+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/oauth1-client",
|
||||||
|
"version": "v1.11.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/oauth1-client.git",
|
||||||
|
"reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
|
||||||
|
"reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||||
|
"guzzlehttp/psr7": "^1.7|^2.0",
|
||||||
|
"php": ">=7.1||>=8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.17",
|
||||||
|
"mockery/mockery": "^1.3.3",
|
||||||
|
"phpstan/phpstan": "^0.12.42",
|
||||||
|
"phpunit/phpunit": "^7.5||9.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-simplexml": "For decoding XML-based responses."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0-dev",
|
||||||
|
"dev-develop": "2.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\OAuth1\\Client\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ben Corlett",
|
||||||
|
"email": "bencorlett@me.com",
|
||||||
|
"homepage": "http://www.webcomm.com.au",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "OAuth 1.0 Client Library",
|
||||||
|
"keywords": [
|
||||||
|
"Authentication",
|
||||||
|
"SSO",
|
||||||
|
"authorization",
|
||||||
|
"bitbucket",
|
||||||
|
"identity",
|
||||||
|
"idp",
|
||||||
|
"oauth",
|
||||||
|
"oauth1",
|
||||||
|
"single sign on",
|
||||||
|
"trello",
|
||||||
|
"tumblr",
|
||||||
|
"twitter"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/oauth1-client/issues",
|
||||||
|
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0"
|
||||||
|
},
|
||||||
|
"time": "2024-12-10T19:59:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "league/pipeline",
|
"name": "league/pipeline",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -4038,6 +4135,125 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-05-08T08:14:37+00:00"
|
"time": "2025-05-08T08:14:37+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/constant_time_encoding",
|
||||||
|
"version": "v3.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||||
|
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||||
|
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"infection/infection": "^0",
|
||||||
|
"nikic/php-fuzzer": "^0",
|
||||||
|
"phpunit/phpunit": "^9|^10|^11",
|
||||||
|
"vimeo/psalm": "^4|^5|^6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ParagonIE\\ConstantTime\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com",
|
||||||
|
"homepage": "https://paragonie.com",
|
||||||
|
"role": "Maintainer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Steve 'Sc00bz' Thomas",
|
||||||
|
"email": "steve@tobtu.com",
|
||||||
|
"homepage": "https://www.tobtu.com",
|
||||||
|
"role": "Original Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||||
|
"keywords": [
|
||||||
|
"base16",
|
||||||
|
"base32",
|
||||||
|
"base32_decode",
|
||||||
|
"base32_encode",
|
||||||
|
"base64",
|
||||||
|
"base64_decode",
|
||||||
|
"base64_encode",
|
||||||
|
"bin2hex",
|
||||||
|
"encoding",
|
||||||
|
"hex",
|
||||||
|
"hex2bin",
|
||||||
|
"rfc4648"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "info@paragonie.com",
|
||||||
|
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||||
|
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||||
|
},
|
||||||
|
"time": "2025-09-24T15:06:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/random_compat",
|
||||||
|
"version": "v9.99.100",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/random_compat.git",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">= 7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "4.*|5.*",
|
||||||
|
"vimeo/psalm": "^1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com",
|
||||||
|
"homepage": "https://paragonie.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||||
|
"keywords": [
|
||||||
|
"csprng",
|
||||||
|
"polyfill",
|
||||||
|
"pseudorandom",
|
||||||
|
"random"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "info@paragonie.com",
|
||||||
|
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||||
|
"source": "https://github.com/paragonie/random_compat"
|
||||||
|
},
|
||||||
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "php-http/discovery",
|
"name": "php-http/discovery",
|
||||||
"version": "1.20.0",
|
"version": "1.20.0",
|
||||||
@@ -4192,6 +4408,116 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-08-21T11:53:16+00:00"
|
"time": "2025-08-21T11:53:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpseclib/phpseclib",
|
||||||
|
"version": "3.0.48",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||||
|
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||||
|
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"paragonie/constant_time_encoding": "^1|^2|^3",
|
||||||
|
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||||
|
"php": ">=5.6.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
|
||||||
|
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||||
|
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||||
|
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||||
|
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"phpseclib/bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"phpseclib3\\": "phpseclib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jim Wigginton",
|
||||||
|
"email": "terrafrost@php.net",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Patrick Monnerat",
|
||||||
|
"email": "pm@datasphere.ch",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Andreas Fischer",
|
||||||
|
"email": "bantu@phpbb.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hans-Jürgen Petrich",
|
||||||
|
"email": "petrich@tronic-media.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "graham@alt-three.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
|
||||||
|
"homepage": "http://phpseclib.sourceforge.net",
|
||||||
|
"keywords": [
|
||||||
|
"BigInteger",
|
||||||
|
"aes",
|
||||||
|
"asn.1",
|
||||||
|
"asn1",
|
||||||
|
"blowfish",
|
||||||
|
"crypto",
|
||||||
|
"cryptography",
|
||||||
|
"encryption",
|
||||||
|
"rsa",
|
||||||
|
"security",
|
||||||
|
"sftp",
|
||||||
|
"signature",
|
||||||
|
"signing",
|
||||||
|
"ssh",
|
||||||
|
"twofish",
|
||||||
|
"x.509",
|
||||||
|
"x509"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||||
|
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/terrafrost",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/phpseclib",
|
||||||
|
"type": "patreon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-12-15T11:51:42+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "predis/predis",
|
"name": "predis/predis",
|
||||||
"version": "v2.4.0",
|
"version": "v2.4.0",
|
||||||
@@ -5147,6 +5473,130 @@
|
|||||||
},
|
},
|
||||||
"time": "2022-04-25T22:18:50+00:00"
|
"time": "2022-04-25T22:18:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "socialiteproviders/discord",
|
||||||
|
"version": "4.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SocialiteProviders/Discord.git",
|
||||||
|
"reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/c71c379acfdca5ba4aa65a3db5ae5222852a919c",
|
||||||
|
"reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": "^7.4 || ^8.0",
|
||||||
|
"socialiteproviders/manager": "~4.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SocialiteProviders\\Discord\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christopher Eklund",
|
||||||
|
"email": "eklundchristopher@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Discord OAuth2 Provider for Laravel Socialite",
|
||||||
|
"keywords": [
|
||||||
|
"discord",
|
||||||
|
"laravel",
|
||||||
|
"oauth",
|
||||||
|
"provider",
|
||||||
|
"socialite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://socialiteproviders.com/discord",
|
||||||
|
"issues": "https://github.com/socialiteproviders/providers/issues",
|
||||||
|
"source": "https://github.com/socialiteproviders/providers"
|
||||||
|
},
|
||||||
|
"time": "2023-07-24T23:28:47+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "socialiteproviders/manager",
|
||||||
|
"version": "v4.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||||
|
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||||
|
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
|
||||||
|
"laravel/socialite": "^5.5",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.2",
|
||||||
|
"phpunit/phpunit": "^9.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"SocialiteProviders\\Manager\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SocialiteProviders\\Manager\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Wendt",
|
||||||
|
"email": "andy@awendt.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anton Komarev",
|
||||||
|
"email": "a.komarev@cybercog.su"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Miguel Piedrafita",
|
||||||
|
"email": "soy@miguelpiedrafita.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atymic",
|
||||||
|
"email": "atymicq@gmail.com",
|
||||||
|
"homepage": "https://atymic.dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily add new or override built-in providers in Laravel Socialite.",
|
||||||
|
"homepage": "https://socialiteproviders.com",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"manager",
|
||||||
|
"oauth",
|
||||||
|
"providers",
|
||||||
|
"socialite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/socialiteproviders/manager/issues",
|
||||||
|
"source": "https://github.com/socialiteproviders/manager"
|
||||||
|
},
|
||||||
|
"time": "2025-02-24T19:33:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/browsershot",
|
"name": "spatie/browsershot",
|
||||||
"version": "5.0.11",
|
"version": "5.0.11",
|
||||||
@@ -8835,6 +9285,67 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-04-30T06:54:44+00:00"
|
"time": "2025-04-30T06:54:44+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/breeze",
|
||||||
|
"version": "v2.3.8",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/breeze.git",
|
||||||
|
"reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/breeze/zipball/1a29c5792818bd4cddf70b5f743a227e02fbcfcd",
|
||||||
|
"reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/console": "^11.0|^12.0",
|
||||||
|
"illuminate/filesystem": "^11.0|^12.0",
|
||||||
|
"illuminate/support": "^11.0|^12.0",
|
||||||
|
"illuminate/validation": "^11.0|^12.0",
|
||||||
|
"php": "^8.2.0",
|
||||||
|
"symfony/console": "^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/framework": "^11.0|^12.0",
|
||||||
|
"orchestra/testbench-core": "^9.0|^10.0",
|
||||||
|
"phpstan/phpstan": "^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Breeze\\BreezeServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Breeze\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.",
|
||||||
|
"keywords": [
|
||||||
|
"auth",
|
||||||
|
"laravel"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/breeze/issues",
|
||||||
|
"source": "https://github.com/laravel/breeze"
|
||||||
|
},
|
||||||
|
"time": "2025-07-18T18:49:59+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/pint",
|
"name": "laravel/pint",
|
||||||
"version": "v1.25.1",
|
"version": "v1.25.1",
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'invite_link' => 'https://discord.gg/yAqgVKNgG5',
|
'invite_link' => 'https://discord.gg/yAqgVKNgG5',
|
||||||
|
|
||||||
'guild_id' => 802233383710228550,
|
'guild_id' => 802233383710228550,
|
||||||
|
|
||||||
'patreon_roles' => [841798154999169054, 803329707650187364, 803327903659196416, 803325441942356059, 803322725576736858, 802270568912519198, 802234830384267315],
|
'patreon_roles' => [
|
||||||
|
841798154999169054, // ????
|
||||||
|
803329707650187364, // Tier-5
|
||||||
|
803327903659196416, // ????
|
||||||
|
803325441942356059, // Tier-3
|
||||||
|
803322725576736858, // Tier-2
|
||||||
|
802270568912519198, // Tier-1
|
||||||
|
802234830384267315 // admin
|
||||||
|
],
|
||||||
|
|
||||||
|
'discord_bot_token' => env('DISCORD_BOT_TOKEN'),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Application ID
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the ID of your Discord application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'client_id' => env('LARASCORD_CLIENT_ID', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Application Secret
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the secret of your Discord application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'client_secret' => env('LARASCORD_CLIENT_SECRET', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Application Access Token
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the access token of your Discord application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'access_token' => env('LARASCORD_ACCESS_TOKEN', null),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Grant Type
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the grant type of your Discord application. It must be set to
|
|
||||||
| "authorization_code".
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'grant_type' => env('LARASCORD_GRANT_TYPE', 'authorization_code'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Redirect URI
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the URI that Discord will redirect to after the user authorizes
|
|
||||||
| your application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'redirect_uri' => env('APP_URL', 'http://localhost:8000') . '/' . env('LARASCORD_PREFIX', 'larascord') . '/callback',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Scopes
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| These are the OAuth2 scopes of your Discord application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'scopes' => env('LARASCORD_SCOPE', 'identify&email&guilds&guilds.members.read'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Route Prefix
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is the prefix that Larascord will use for its routes. For example,
|
|
||||||
| the prefix "larascord" will result in the route
|
|
||||||
| "https://domain.com/larascord/login".
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'route_prefix' => env('LARASCORD_PREFIX', 'larascord'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| OAuth2 Prompt - "none" or "consent"
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The prompt controls how the authorization flow handles existing authorizations.
|
|
||||||
| If a user has previously authorized your application with the requested scopes
|
|
||||||
| and prompt is set to consent,it will request them to re-approve their
|
|
||||||
| authorization. If set to none, it will skip the authorization screen
|
|
||||||
| and redirect them back to your redirect URI without requesting
|
|
||||||
| their authorization.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'prompt' => 'none',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Restrict Access to Specific Guilds
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This option restricts access to the application to users who are members
|
|
||||||
| of specific Discord guilds. Users who are not members of the specified
|
|
||||||
| guilds will not be able to use the application.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'guilds' => [],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Restrict Access to Specific Guilds - Strict Mode
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Enabling this option will require the user to be a member of ALL the
|
|
||||||
| aforementioned guilds. If this option is disabled, the user will
|
|
||||||
| only need to be a member of at least ONE of the guilds.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'guilds_strict' => false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Restrict Access to Specific Roles
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When this option is enabled, the user will only be able to use the
|
|
||||||
| application if they have at least one of the specified roles.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
// WARNING: This feature makes one request to the Discord API for each guild you specify. (Because you need to fetch the roles for each guild)
|
|
||||||
// At the moment the database is not checked for roles when the user logs in. It will always fetch the roles from the Discord API.
|
|
||||||
// Currently, the roles are only updated in the database when the user logs in. The roles from the database can be used in a middleware.
|
|
||||||
// I'm working on a better way to do this, but for now, this will work.
|
|
||||||
|
|
||||||
'guild_roles' => [
|
|
||||||
// 'guild_id' => [
|
|
||||||
// 'role_id',
|
|
||||||
// 'role_id',
|
|
||||||
// ],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Remember Me
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Whether or not to remember the user after they log in.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'remember_me' => true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Error Messages
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| These are the error messages that will be displayed to the user if there
|
|
||||||
| is an error.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'error_messages' => [
|
|
||||||
'missing_code' => [
|
|
||||||
'message' => 'The authorization code is missing.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'invalid_code' => [
|
|
||||||
'message' => 'The authorization code is invalid.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'authorization_failed' => [
|
|
||||||
'message' => 'The authorization failed.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'missing_email' => [
|
|
||||||
'message' => 'Couldn\'t get your e-mail address. Please add an e-mail address to your Discord account!',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'invalid_user' => [
|
|
||||||
'message' => 'The user ID doesn\'t match the logged-in user.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'database_error' => [
|
|
||||||
'message' => 'There was an error with the database. Please try again later.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'missing_guilds_scope' => [
|
|
||||||
'message' => 'The "guilds" scope is required.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'missing_guilds_members_read_scope' => [
|
|
||||||
'message' => 'The "guilds" and "guilds.members.read" scopes are required.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'authorization_failed_guilds' => [
|
|
||||||
'message' => 'Couldn\'t get the servers you\'re in.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'not_member_guild_only' => [
|
|
||||||
'message' => 'You are not a member of the required guilds.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'missing_access_token' => [
|
|
||||||
'message' => 'The access token is missing.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'authorization_failed_roles' => [
|
|
||||||
'message' => 'Couldn\'t get the roles you have.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'missing_role' => [
|
|
||||||
'message' => 'You don\'t have the required roles.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
'revoke_token_failed' => [
|
|
||||||
'message' => 'An error occurred while trying to revoke your access token.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Success Messages
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| These are the success messages that will be displayed to the user if there
|
|
||||||
| is no error.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'success_messages' => [
|
|
||||||
'user_deleted' => [
|
|
||||||
'message' => 'Your account has been deleted.',
|
|
||||||
'redirect' => '/'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -31,4 +31,21 @@ return [
|
|||||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Socialite Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
'discord' => [
|
||||||
|
'client_id' => env('DISCORD_CLIENT_ID'),
|
||||||
|
'client_secret' => env('DISCORD_CLIENT_SECRET'),
|
||||||
|
'redirect' => '/auth/discord/callback',
|
||||||
|
|
||||||
|
// optional
|
||||||
|
'allow_gif_avatars' => (bool) env('DISCORD_AVATAR_GIF', true),
|
||||||
|
'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'webp'), // only pick from jpg, png, webp
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use App\Models\Downloads;
|
|||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
# Delete entries with "#" as URL
|
# Delete entries with "#" as URL
|
||||||
Downloads::where('url', '#')->forceDelete();
|
Downloads::where('url', '#')->delete();
|
||||||
|
|
||||||
# Remove duplicate entries
|
# Remove duplicate entries
|
||||||
$duplicates = DB::table('downloads')
|
$duplicates = DB::table('downloads')
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Remove tables from larascord
|
||||||
|
Schema::dropIfExists('discord_access_tokens');
|
||||||
|
Schema::dropIfExists('personal_access_tokens');
|
||||||
|
|
||||||
|
// Drop columns from larascord
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('discriminator');
|
||||||
|
$table->dropColumn('remember_token');
|
||||||
|
$table->dropColumn('banner');
|
||||||
|
$table->dropColumn('banner_color');
|
||||||
|
$table->dropColumn('accent_color');
|
||||||
|
$table->dropColumn('premium_type');
|
||||||
|
$table->dropColumn('public_flags');
|
||||||
|
$table->dropColumn('verified');
|
||||||
|
$table->dropColumn('mfa_enabled');
|
||||||
|
$table->dropColumn('global_name');
|
||||||
|
$table->dropColumn('locale');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change & Add Columns
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// Rename
|
||||||
|
$table->renameColumn('username', 'name');
|
||||||
|
$table->renameColumn('avatar', 'discord_avatar');
|
||||||
|
$table->string('avatar')->nullable()->after('email');
|
||||||
|
|
||||||
|
// Re-Add Email verification
|
||||||
|
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||||
|
|
||||||
|
// Re-Add Password Auth
|
||||||
|
$table->string('password')->nullable()->after('email_verified_at');
|
||||||
|
$table->rememberToken()->after('password');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* Fix Discord Profile Pictures
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* The oauth package by socialite now returns a full url of the avatar.
|
||||||
|
* Meaning all the old entries have to be fixed.
|
||||||
|
*/
|
||||||
|
foreach (User::whereNotNull('discord_avatar')->get() as $user)
|
||||||
|
{
|
||||||
|
$isGif = preg_match('/a_.+/m', $user->discord_avatar) === 1;
|
||||||
|
$extension = $isGif ? 'gif' : 'webp';
|
||||||
|
$user->discord_avatar = sprintf('https://cdn.discordapp.com/avatars/%s/%s.%s', $user->id, $user->discord_avatar, $extension);
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
207
database/migrations/2026_01_08_213625_fix_database_structure.php
Normal file
207
database/migrations/2026_01_08_213625_fix_database_structure.php
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Playlist;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// 1. Create new column discord_id
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('discord_id')->nullable()->after('id');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Migrate Discord Users IDs
|
||||||
|
DB::table('users')
|
||||||
|
->where('id', '>', 10000)
|
||||||
|
->update(['discord_id' => DB::raw('id')]);
|
||||||
|
|
||||||
|
// 3. Temporary new auto increment column
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('new_id')->first();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3.5 Count
|
||||||
|
DB::statement('
|
||||||
|
UPDATE users u
|
||||||
|
JOIN (
|
||||||
|
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn
|
||||||
|
FROM users
|
||||||
|
) t ON u.id = t.id
|
||||||
|
SET u.new_id = t.rn
|
||||||
|
');
|
||||||
|
|
||||||
|
// 4. Drop foreign keys
|
||||||
|
$this->dropForeignKeys();
|
||||||
|
|
||||||
|
// 5. Fix ID's in other tables
|
||||||
|
$this->updateUserIDsInOtherTables();
|
||||||
|
|
||||||
|
// 6. Remove old ID
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('id')->unsigned()->change();
|
||||||
|
$table->dropPrimary('id');
|
||||||
|
$table->dropColumn('id');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. Rename new_id to id
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('new_id', 'id');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 8. Change new ID to auto increment and set as primary key
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('id')->autoIncrement()->primary()->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 9. Remove data that would conflict with constraints
|
||||||
|
$this->deleteUnreferencedData();
|
||||||
|
|
||||||
|
// 9. Recreate foreign key constraints
|
||||||
|
$this->addForeignKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop Foreign Keys referencing the user id
|
||||||
|
*/
|
||||||
|
private function dropForeignKeys(): void
|
||||||
|
{
|
||||||
|
Schema::table('markable_likes', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['user_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('watched', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['user_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Our Schema does include a foreign key, for whatever reason it doesn't exist in the first palce
|
||||||
|
// Schema::table('user_downloads', function (Blueprint $table) {
|
||||||
|
// $table->dropForeign(['user_id']);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tables to fix the IDs:
|
||||||
|
* - comments ['commenter_id']
|
||||||
|
* - markable_likes ['user_id']
|
||||||
|
* - notifications ['notifiable_id']
|
||||||
|
* - playlists ['user_id']
|
||||||
|
* - user_downloads ['user_id']
|
||||||
|
* - watched ['user_id']
|
||||||
|
*/
|
||||||
|
private function updateUserIDsInOtherTables(): void
|
||||||
|
{
|
||||||
|
DB::statement('
|
||||||
|
UPDATE comments c
|
||||||
|
JOIN users u ON c.commenter_id = u.id
|
||||||
|
SET c.commenter_id = u.new_id
|
||||||
|
');
|
||||||
|
|
||||||
|
DB::statement('
|
||||||
|
UPDATE watched w
|
||||||
|
JOIN users u ON w.user_id = u.id
|
||||||
|
SET w.user_id = u.new_id
|
||||||
|
');
|
||||||
|
|
||||||
|
DB::statement('
|
||||||
|
UPDATE markable_likes ml
|
||||||
|
JOIN users u ON ml.user_id = u.id
|
||||||
|
SET ml.user_id = u.new_id
|
||||||
|
');
|
||||||
|
|
||||||
|
DB::statement('
|
||||||
|
UPDATE notifications n
|
||||||
|
JOIN users u ON n.notifiable_id = u.id
|
||||||
|
SET n.notifiable_id = u.new_id
|
||||||
|
');
|
||||||
|
|
||||||
|
DB::statement('
|
||||||
|
UPDATE playlists p
|
||||||
|
JOIN users u ON p.user_id = u.id
|
||||||
|
SET p.user_id = u.new_id
|
||||||
|
');
|
||||||
|
|
||||||
|
DB::statement('
|
||||||
|
UPDATE user_downloads ud
|
||||||
|
JOIN users u ON ud.user_id = u.id
|
||||||
|
SET ud.user_id = u.new_id
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to incorrect handling of user deletes,
|
||||||
|
* we have unreferenced data
|
||||||
|
*/
|
||||||
|
private function deleteUnreferencedData(): void
|
||||||
|
{
|
||||||
|
// User Downloads Table
|
||||||
|
DB::table('user_downloads')
|
||||||
|
->where('user_id', '>', 1_000_000)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
// User Playlists Table
|
||||||
|
$playlists = Playlist::where('user_id', '>', 1_000_000)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach($playlists as $playlist) {
|
||||||
|
DB::table('playlist_episodes')
|
||||||
|
->where('playlist_id', '=', $playlist->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$playlist->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-Add Foreign Keys to tables which we dropped previously
|
||||||
|
*/
|
||||||
|
private function addForeignKeys(): void
|
||||||
|
{
|
||||||
|
Schema::table('markable_likes', function (Blueprint $table) {
|
||||||
|
// Ensure the column is unsigned
|
||||||
|
$table->bigInteger('user_id')->unsigned()->change();
|
||||||
|
|
||||||
|
// Add the foreign key constraint
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('watched', function (Blueprint $table) {
|
||||||
|
// Ensure the column is unsigned
|
||||||
|
$table->bigInteger('user_id')->unsigned()->change();
|
||||||
|
|
||||||
|
// Add the foreign key constraint
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('user_downloads', function (Blueprint $table) {
|
||||||
|
// Ensure the column is unsigned
|
||||||
|
$table->bigInteger('user_id')->unsigned()->change();
|
||||||
|
|
||||||
|
// Add the foreign key constraint
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('playlist_episodes', function (Blueprint $table) {
|
||||||
|
// Ensure the column is unsigned
|
||||||
|
$table->bigInteger('playlist_id')->unsigned()->change();
|
||||||
|
|
||||||
|
// Add the foreign key constraint
|
||||||
|
$table->foreign('playlist_id')->references('id')->on('playlists')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('playlists', function (Blueprint $table) {
|
||||||
|
// Ensure the column is unsigned
|
||||||
|
$table->bigInteger('user_id')->unsigned()->change();
|
||||||
|
|
||||||
|
// Add the foreign key constraint
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
36
package-lock.json
generated
36
package-lock.json
generated
@@ -16,12 +16,13 @@
|
|||||||
"vidstack": "^1.12.13"
|
"vidstack": "^1.12.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"autoprefixer": "^10.4.18",
|
"alpinejs": "^3.4.2",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.31",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.1.0",
|
||||||
"vite": "^7.1.6",
|
"vite": "^7.1.6",
|
||||||
"vite-plugin-static-copy": "^3.0.1"
|
"vite-plugin-static-copy": "^3.0.1"
|
||||||
}
|
}
|
||||||
@@ -974,6 +975,23 @@
|
|||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/reactivity": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/shared": "3.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/shared": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@yaireo/tagify": {
|
"node_modules/@yaireo/tagify": {
|
||||||
"version": "4.35.4",
|
"version": "4.35.4",
|
||||||
"resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.4.tgz",
|
"resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.4.tgz",
|
||||||
@@ -1001,6 +1019,16 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/alpinejs": {
|
||||||
|
"version": "3.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.3.tgz",
|
||||||
|
"integrity": "sha512-fSI6F5213FdpMC4IWaup92KhuH3jBX0VVqajRJ6cOTCy1cL6888KyXdGO+seAAkn+g6fnrxBqQEx6gRpQ5EZoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "~3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
"build": "vite build"
|
"build": "vite build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"autoprefixer": "^10.4.18",
|
"alpinejs": "^3.4.2",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.31",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.1.0",
|
||||||
"vite": "^7.1.6",
|
"vite": "^7.1.6",
|
||||||
"vite-plugin-static-copy": "^3.0.1"
|
"vite-plugin-static-copy": "^3.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
import 'hammerjs';
|
||||||
// import { Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
|
|
||||||
// Alpine.start();
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapse,
|
Collapse,
|
||||||
Carousel,
|
Carousel,
|
||||||
@@ -15,6 +12,10 @@ import {
|
|||||||
initTE,
|
initTE,
|
||||||
} from "tw-elements";
|
} from "tw-elements";
|
||||||
|
|
||||||
initTE({ Collapse, Carousel, Clipboard, Modal, Tab, Lightbox, Tooltip, Ripple });
|
import Alpine from 'alpinejs';
|
||||||
|
|
||||||
import 'hammerjs';
|
window.Alpine = Alpine;
|
||||||
|
|
||||||
|
Alpine.start();
|
||||||
|
|
||||||
|
initTE({ Collapse, Carousel, Clipboard, Modal, Tab, Lightbox, Tooltip, Ripple });
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
<x-guest-layout>
|
<x-guest-layout>
|
||||||
|
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __('This is a secure area of the application. Please confirm your session before continuing.') }}
|
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="POST" action="{{ route('larascord.refresh_token') }}">
|
<form method="POST" action="{{ route('password.confirm') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required autocomplete="current-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end mt-4">
|
<div class="flex justify-end mt-4">
|
||||||
<x-primary-button>
|
<x-primary-button>
|
||||||
<svg style="margin-right: 10px;" width="30px" height="30px" viewBox="0 -28.5 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
|
||||||
<g>
|
|
||||||
<path d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z" fill="#ffff" fill-rule="nonzero"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
{{ __('Confirm') }}
|
{{ __('Confirm') }}
|
||||||
</x-primary-button>
|
</x-primary-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</x-guest-layout>
|
</x-guest-layout>
|
||||||
27
resources/views/auth/forgot-password.blade.php
Normal file
27
resources/views/auth/forgot-password.blade.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<x-guest-layout>
|
||||||
|
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Session Status -->
|
||||||
|
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.email') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Send Password Reset Link') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
138
resources/views/auth/login.blade.php
Normal file
138
resources/views/auth/login.blade.php
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<x-guest-layout>
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="w-full sm:max-w-md mt-6">
|
||||||
|
<ul class="flex list-none flex-row flex-wrap border-b-0 pl-0 relative " role="tablist" data-te-nav-ref>
|
||||||
|
<li role="presentation" class="flex-auto text-center">
|
||||||
|
<a href="#tabs-login" class="rounded-l-lg my-2 block border-x-0 border-b-2 border-t-0 border-transparent px-7 pb-3.5 pt-4 text-xs font-medium uppercase leading-tight text-neutral-500 hover:isolate hover:border-transparent hover:bg-neutral-50 focus:isolate focus:border-transparent data-[te-nav-active]:border-rose-600 data-[te-nav-active]:text-black dark:text-neutral-400 bg-white/50 dark:bg-neutral-950/50 backdrop-blur-sm dark:hover:bg-neutral-800 dark:data-[te-nav-active]:text-white"
|
||||||
|
data-te-toggle="pill" data-te-target="#tabs-login" data-te-nav-active role="tab" aria-controls="tabs-login" aria-selected="true">
|
||||||
|
{{ __('Login') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation" class="flex-auto text-center">
|
||||||
|
<a href="#tabs-register" class="rounded-r-lg my-2 block border-x-0 border-b-2 border-t-0 border-transparent px-7 pb-3.5 pt-4 text-xs font-medium uppercase leading-tight text-neutral-500 hover:isolate hover:border-transparent hover:bg-neutral-50 focus:isolate focus:border-transparent data-[te-nav-active]:border-rose-600 data-[te-nav-active]:text-black dark:text-neutral-400 bg-white/50 dark:bg-neutral-950/50 backdrop-blur-sm dark:hover:bg-neutral-800 dark:data-[te-nav-active]:text-white"
|
||||||
|
data-te-toggle="pill" data-te-target="#tabs-register" role="tab" aria-controls="tabs-register" aria-selected="false">
|
||||||
|
{{ __('Register') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login -->
|
||||||
|
<div class="w-full sm:max-w-md hidden opacity-100 transition-opacity duration-150 ease-linear data-[te-tab-active]:block" id="tabs-login" role="tabpanel" aria-labelledby="tabs-login" data-te-tab-active>
|
||||||
|
<div class="px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="w-full text-center text-white mb-3">
|
||||||
|
<a href="{{ route('discord.login') }}">
|
||||||
|
<div
|
||||||
|
class="relative bg-blue-700 hover:bg-blue-600 text-white font-bold px-4 h-10 rounded text-center p-[10px] mb-4">
|
||||||
|
<i class="fa-brands fa-discord"></i> {{ __('Use Discord Account') }}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Or -->
|
||||||
|
<div class="grid grid-cols-3">
|
||||||
|
<hr class="self-center border-neutral-600">
|
||||||
|
<p>OR</p>
|
||||||
|
<hr class="self-center border-neutral-600">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Session Status -->
|
||||||
|
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('login') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required autocomplete="current-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Remember Me -->
|
||||||
|
<div class="block mt-4">
|
||||||
|
<label for="remember_me" class="inline-flex items-center">
|
||||||
|
<input id="remember_me" type="checkbox" class="rounded dark:bg-neutral-900 border-neutral-300 dark:border-neutral-700 text-rose-600 shadow-sm focus:ring-rose-500 dark:focus:ring-rose-600 dark:focus:ring-offset-neutral-800" name="remember">
|
||||||
|
<span class="ms-2 text-sm text-neutral-600 dark:text-neutral-400">{{ __('Remember me') }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
@if (Route::has('password.request'))
|
||||||
|
<a class="underline text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 dark:focus:ring-offset-neutral-800" href="{{ route('password.request') }}">
|
||||||
|
{{ __('Forgot your password?') }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-primary-button class="ms-3">
|
||||||
|
{{ __('Log in') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Register -->
|
||||||
|
<div class="w-full sm:max-w-md hidden opacity-0 transition-opacity duration-150 ease-linear data-[te-tab-active]:block" id="tabs-register" role="tabpanel" aria-labelledby="tabs-register">
|
||||||
|
<div class="px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
|
<form method="POST" action="{{ route('register') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="name" :value="__('Name')" />
|
||||||
|
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
|
||||||
|
<x-input-error :messages="$errors->get('name')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required autocomplete="new-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password_confirmation" required autocomplete="new-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-primary-button class="ms-4">
|
||||||
|
{{ __('Register') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
41
resources/views/auth/reset-password.blade.php
Normal file
41
resources/views/auth/reset-password.blade.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<x-guest-layout>
|
||||||
|
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
|
<form method="POST" action="{{ route('password.store') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Password Reset Token -->
|
||||||
|
<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password_confirmation" required autocomplete="new-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Reset Password') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
33
resources/views/auth/verify-email.blade.php
Normal file
33
resources/views/auth/verify-email.blade.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<x-guest-layout>
|
||||||
|
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-neutral-950/50 shadow-md overflow-hidden sm:rounded-lg">
|
||||||
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (session('status') == 'verification-link-sent')
|
||||||
|
<div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400">
|
||||||
|
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<form method="POST" action="{{ route('verification.send') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Resend Verification Email') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<button type="submit" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
|
||||||
|
{{ __('Log Out') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
@@ -1 +1 @@
|
|||||||
<img class="h-10" src="/images/hs_banner.png">
|
<img class="h-16" src="/images/hs_banner.png">
|
||||||
@@ -1 +1 @@
|
|||||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-neutral-100 dark:hover:bg-neutral-900 focus:outline-none focus:bg-neutral-100 dark:focus:bg-neutral-800 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-neutral-100 dark:hover:bg-neutral-900 focus:outline-none focus:bg-neutral-100 dark:focus:bg-neutral-800 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white dark:bg-neutral-800'])
|
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white dark:bg-neutral-800'])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
switch ($align) {
|
$alignmentClasses = match ($align) {
|
||||||
case 'left':
|
'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
|
||||||
$alignmentClasses = 'origin-top-left left-0';
|
'top' => 'origin-top',
|
||||||
break;
|
default => 'ltr:origin-top-right rtl:origin-top-left end-0',
|
||||||
case 'top':
|
};
|
||||||
$alignmentClasses = 'origin-top';
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
default:
|
|
||||||
$alignmentClasses = 'origin-top-right right-0';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($width) {
|
$width = match ($width) {
|
||||||
case '48':
|
'48' => 'w-48',
|
||||||
$width = 'w-48';
|
default => $width,
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||||
@@ -28,11 +20,11 @@ switch ($width) {
|
|||||||
|
|
||||||
<div x-show="open"
|
<div x-show="open"
|
||||||
x-transition:enter="transition ease-out duration-200"
|
x-transition:enter="transition ease-out duration-200"
|
||||||
x-transition:enter-start="transform opacity-0 scale-95"
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
x-transition:enter-end="transform opacity-100 scale-100"
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
x-transition:leave="transition ease-in duration-75"
|
x-transition:leave="transition ease-in duration-75"
|
||||||
x-transition:leave-start="transform opacity-100 scale-100"
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
x-transition:leave-end="transform opacity-0 scale-95"
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
@click="open = false">
|
@click="open = false">
|
||||||
|
|||||||
@@ -40,12 +40,13 @@ $maxWidth = [
|
|||||||
}
|
}
|
||||||
})"
|
})"
|
||||||
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||||
|
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||||
x-on:close.stop="show = false"
|
x-on:close.stop="show = false"
|
||||||
x-on:keydown.escape.window="show = false"
|
x-on:keydown.escape.window="show = false"
|
||||||
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||||
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||||
x-show="show"
|
x-show="show"
|
||||||
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
class="fixed inset-0 overflow-y-auto px-4 py-12 sm:px-0 z-50"
|
||||||
style="display: {{ $show ? 'block' : 'none' }};"
|
style="display: {{ $show ? 'block' : 'none' }};"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -59,12 +60,12 @@ $maxWidth = [
|
|||||||
x-transition:leave-start="opacity-100"
|
x-transition:leave-start="opacity-100"
|
||||||
x-transition:leave-end="opacity-0"
|
x-transition:leave-end="opacity-0"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
<div class="absolute inset-0 bg-neutral-500 dark:bg-neutral-900 opacity-75"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
x-show="show"
|
x-show="show"
|
||||||
class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
class="mb-6 bg-white dark:bg-neutral-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||||
x-transition:enter="ease-out duration-300"
|
x-transition:enter="ease-out duration-300"
|
||||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
@props(['active'])
|
@props(['active'])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$classes =
|
$classes = ($active ?? false)
|
||||||
$active ?? false
|
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out'
|
||||||
? 'block w-full pl-3 pr-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-left text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out'
|
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600 focus:outline-none focus:text-neutral-800 dark:focus:text-neutral-200 focus:bg-neutral-50 dark:focus:bg-neutral-700 focus:border-neutral-300 dark:focus:border-neutral-600 transition duration-150 ease-in-out';
|
||||||
: 'block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-left text-base font-medium text-neutral-600 dark:text-neutral-200 hover:text-neutral-800 dark:hover:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600 focus:outline-none focus:text-neutral-800 dark:focus:text-neutral-200 focus:bg-neutral-50 dark:focus:bg-neutral-700 focus:border-neutral-300 dark:focus:border-neutral-600 transition duration-150 ease-in-out';
|
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-500 rounded-md font-semibold text-xs text-neutral-700 dark:text-neutral-300 uppercase tracking-widest shadow-sm hover:bg-neutral-50 dark:hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 dark:focus:ring-offset-neutral-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
@props(['disabled' => false])
|
@props(['disabled' => false])
|
||||||
|
|
||||||
<input {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600 rounded-md shadow-sm']) !!}>
|
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600 rounded-md shadow-sm']) }}>
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
|
|
||||||
@include('partials.head')
|
@include('partials.head')
|
||||||
|
|
||||||
<body class="font-sans text-gray-900 antialiased">
|
<body class="font-sans antialiased">
|
||||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
|
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-neutral-900">
|
||||||
<div>
|
<div>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
|
<x-application-logo class="w-24 h-24 fill-current text-gray-500" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md overflow-hidden sm:rounded-lg">
|
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -94,11 +94,9 @@
|
|||||||
<button
|
<button
|
||||||
class="inline-flex items-center px-3 py-2 border text-sm leading-4 font-medium rounded-md text-gray-500 border-neutral-300/50 dark:text-gray-400 bg-white/20 dark:bg-neutral-950/20 dark:border-neutral-800/50 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
|
class="inline-flex items-center px-3 py-2 border text-sm leading-4 font-medium rounded-md text-gray-500 border-neutral-300/50 dark:text-gray-400 bg-white/20 dark:bg-neutral-950/20 dark:border-neutral-800/50 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
|
||||||
@auth
|
@auth
|
||||||
@if (Auth::user()->avatar)
|
|
||||||
<img class="h-8 w-8 rounded-full object-cover mr-2"
|
<img class="h-8 w-8 rounded-full object-cover mr-2"
|
||||||
src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ Auth::user()->id }}/{{ Auth::user()->avatar }}.webp"
|
src="{{ Auth::user()->getAvatar() }}"
|
||||||
alt="{{ Auth::user()->getTagAttribute() }}" />
|
alt="{{ Auth::user()->name }}" />
|
||||||
@endif
|
|
||||||
@else
|
@else
|
||||||
<img class="h-8 w-8 rounded-full object-cover mr-2" src="/images/default-avatar.webp"
|
<img class="h-8 w-8 rounded-full object-cover mr-2" src="/images/default-avatar.webp"
|
||||||
alt="Guest" />
|
alt="Guest" />
|
||||||
@@ -106,7 +104,7 @@
|
|||||||
|
|
||||||
@auth
|
@auth
|
||||||
<div style="display: flex; flex-direction: row; align-items: flex-start;">
|
<div style="display: flex; flex-direction: row; align-items: flex-start;">
|
||||||
{{ Auth::user()->getTagAttribute() }}
|
{{ Auth::user()->name }}
|
||||||
@if ($notAvailable)
|
@if ($notAvailable)
|
||||||
<i class="fa-solid fa-bell text-rose-600"></i>
|
<i class="fa-solid fa-bell text-rose-600"></i>
|
||||||
@endif
|
@endif
|
||||||
@@ -188,8 +186,8 @@
|
|||||||
@guest
|
@guest
|
||||||
<x-dropdown-link :href="route('login')">
|
<x-dropdown-link :href="route('login')">
|
||||||
<div
|
<div
|
||||||
class="relative bg-blue-700 hover:bg-blue-600 text-white font-bold px-4 h-10 rounded text-center p-[10px]">
|
class="relative bg-rose-700 hover:bg-rose-600 text-white font-bold px-4 h-10 rounded text-center p-[10px]">
|
||||||
<i class="fa-brands fa-discord"></i> {{ __('nav.login') }}
|
<i class="fa-solid fa-arrow-right-to-bracket"></i> {{ __('nav.login') }}
|
||||||
</div>
|
</div>
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
@endguest
|
@endguest
|
||||||
@@ -231,15 +229,11 @@
|
|||||||
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600 dark:bg-neutral-900/30">
|
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600 dark:bg-neutral-900/30">
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@if (Auth::user()->avatar)
|
|
||||||
<img class="h-8 w-8 rounded-full object-cover mr-2"
|
<img class="h-8 w-8 rounded-full object-cover mr-2"
|
||||||
src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ Auth::user()->id }}/{{ Auth::user()->avatar }}.webp"
|
src="{{ Auth::user()->getAvatar() }}"
|
||||||
alt="{{ Auth::user()->getTagAttribute() }}" />
|
alt="{{ Auth::user()->name }}" />
|
||||||
@else
|
<span class="font-medium text-base text-gray-800 dark:text-neutral-200">
|
||||||
<img class="h-8 w-8 rounded-full object-cover mr-2" src="/images/default-avatar.webp"
|
{{ Auth::user()->name }}
|
||||||
alt="Guest" />
|
|
||||||
@endif
|
|
||||||
<span class="font-medium text-base text-gray-800 dark:text-neutral-200">{{ Auth::user()->username }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
@foreach($comments as $comment)
|
@foreach($comments as $comment)
|
||||||
<tr wire:key="comment-{{ $comment->id }}" class="bg-white border-t dark:bg-neutral-800 dark:border-pink-700">
|
<tr wire:key="comment-{{ $comment->id }}" class="bg-white border-t dark:bg-neutral-800 dark:border-pink-700">
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<a href="{{ route('user.index', ['username' => $comment->username]) }}">{{ $comment->username }}</a>
|
{{ $comment->name }}
|
||||||
</td>
|
</td>
|
||||||
<th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg">
|
<th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg">
|
||||||
{{ $comment->comment }}
|
{{ $comment->comment }}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
{{ $user->id }}
|
{{ $user->id }}
|
||||||
</th>
|
</th>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
{{ $user->global_name ?? $user->username }}
|
{{ $user->name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
{{ $user->is_patreon ? 'Yes' : 'No' }}
|
{{ $user->is_patreon ? 'Yes' : 'No' }}
|
||||||
|
|||||||
@@ -4,20 +4,14 @@
|
|||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex text-sm font-light bg-neutral-950/50 backdrop-blur-lg rounded-lg p-10 gap-2">
|
<div class="flex text-sm font-light bg-neutral-950/50 backdrop-blur-lg rounded-lg p-10 gap-2">
|
||||||
<a href="{{ route('user.index', ['username' => $playlist->user->username]) }}">
|
<div>
|
||||||
@if ($playlist->user->avatar)
|
|
||||||
<img class="relative w-24 h-24 flex-none rounded-full shadow-lg"
|
<img class="relative w-24 h-24 flex-none rounded-full shadow-lg"
|
||||||
src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ $playlist->user->id }}/{{ $playlist->user->avatar }}.webp">
|
src="{{ $playlist->user->getAvatar() }}">
|
||||||
@else
|
</div>
|
||||||
<img class="relative w-24 h-24 flex-none rounded-full shadow-lg" src="/images/default-avatar.webp">
|
|
||||||
@endif
|
|
||||||
</a>
|
|
||||||
<div class="flex flex-col justify-center flex-1 pl-4">
|
<div class="flex flex-col justify-center flex-1 pl-4">
|
||||||
<h1 class="font-bold text-3xl">{{ $playlist->name }}</h1>
|
<h1 class="font-bold text-3xl">{{ $playlist->name }}</h1>
|
||||||
<p class="font-light text-lg text-neutral-200">Episodes: {{ count($playlistEpisodes) }}</p>
|
<p class="font-light text-lg text-neutral-200">Episodes: {{ count($playlistEpisodes) }}</p>
|
||||||
<p class="font-light text-lg text-neutral-200">Creator: <a
|
<p class="font-light text-lg text-neutral-200">Creator: {{ $playlist->user->name }}</p>
|
||||||
href="{{ route('user.index', ['username' => $playlist->user->username]) }}">{{ $playlist->user->username }}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center pl-4">
|
<div class="flex flex-col justify-center pl-4">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
|
|||||||
@@ -5,21 +5,17 @@
|
|||||||
@endphp
|
@endphp
|
||||||
<div id="comment-{{ $comment->id }}" class="flex rounded-lg bg-white p-1 mb-2 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] dark:bg-neutral-900">
|
<div id="comment-{{ $comment->id }}" class="flex rounded-lg bg-white p-1 mb-2 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] dark:bg-neutral-900">
|
||||||
@php $user = cache()->rememberForever('commentUser'.$comment->commenter_id, fn () => \App\Models\User::where('id', $comment->commenter_id)->first()); @endphp
|
@php $user = cache()->rememberForever('commentUser'.$comment->commenter_id, fn () => \App\Models\User::where('id', $comment->commenter_id)->first()); @endphp
|
||||||
<a class="contents" href="{{ route('user.index', ['username' => $user->username]) }}">
|
<div>
|
||||||
@if($user->avatar)
|
<img class="w-16 h-16 rounded-full m-2" src="{{ $user->getAvatar() }}" alt="{{ $user->name }} Avatar">
|
||||||
<img class="w-16 h-16 rounded-full m-2" src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ $comment->commenter_id }}/{{ $user->avatar }}.webp" alt="{{ $user->global_name ?? $user->username }} Avatar">
|
</div>
|
||||||
@else
|
|
||||||
<img class="w-16 h-16 rounded-full m-2" src="/images/default-avatar.webp" alt="{{ $user->global_name ?? $user->username }} Avatar">
|
|
||||||
@endif
|
|
||||||
</a>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200">
|
<div class="text-gray-800 dark:text-gray-200">
|
||||||
<a href="{{ route('user.index', ['username' => $user->username]) }}">
|
<div>
|
||||||
@if($user->is_patreon)
|
@if($user->is_patreon)
|
||||||
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->global_name ?? $user->username }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
|
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->name }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
|
||||||
@else
|
@else
|
||||||
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->global_name ?? $user->username }} <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
|
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->name }} <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</div>
|
||||||
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
|
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<x-app-layout>
|
|
||||||
<x-slot name="header">
|
|
||||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
|
||||||
{{ __('Profile') }}
|
|
||||||
</h2>
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
|
||||||
<div class="flex flex-row gap-4 flex-wrap">
|
|
||||||
<!-- Profile Image -->
|
|
||||||
<div class="p-2 bg-white dark:bg-neutral-800 shadow rounded-lg flex-none">
|
|
||||||
@if($user->avatar)
|
|
||||||
<img class="w-28 h-28 rounded-lg m-2" src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ $user->id }}/{{ $user->avatar }}.webp" alt="{{ $user->global_name ?? $user->username }} Avatar">
|
|
||||||
@else
|
|
||||||
<img class="w-24 h-24 rounded-lg m-2" src="/images/default-avatar.webp" alt="{{ $user->global_name ?? $user->username }} Avatar">
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Joined -->
|
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-neutral-800 shadow rounded-lg content-center text-center grow">
|
|
||||||
<div class="inline-block rounded-md text-sky-500">
|
|
||||||
<i class="fa-solid fa-clock text-3xl"></i>
|
|
||||||
</div>
|
|
||||||
<h5 class="font-medium dark:text-neutral-300">
|
|
||||||
Joined {{ $user->created_at->format('Y-m') }}
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- View Count -->
|
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-neutral-800 shadow rounded-lg content-center text-center grow">
|
|
||||||
<a href="{{ route('user.watched') }}">
|
|
||||||
<div class="inline-block rounded-md text-sky-500">
|
|
||||||
<i class="fa-solid fa-eye text-3xl"></i>
|
|
||||||
</div>
|
|
||||||
<h5 class="font-medium dark:text-neutral-300">
|
|
||||||
{{ number_format($user->watched->count()) }} views
|
|
||||||
</h5>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Comment Count -->
|
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-neutral-800 shadow rounded-lg content-center text-center grow">
|
|
||||||
<a href="{{ route('profile.comments') }}">
|
|
||||||
<div class="inline-block rounded-md text-sky-500">
|
|
||||||
<i class="fa-solid fa-comment text-3xl"></i>
|
|
||||||
</div>
|
|
||||||
<h5 class="font-medium dark:text-neutral-300">
|
|
||||||
{{ number_format($user->commentCount()) }} comments
|
|
||||||
</h5>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Likes -->
|
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-neutral-800 shadow rounded-lg content-center text-center grow">
|
|
||||||
<a href="{{ route('profile.likes') }}">
|
|
||||||
<div class="inline-block rounded-md text-sky-500">
|
|
||||||
<i class="fa-solid fa-heart text-3xl"></i>
|
|
||||||
</div>
|
|
||||||
<h5 class="font-medium dark:text-neutral-300">
|
|
||||||
{{ number_format($user->likes()) }} likes
|
|
||||||
</h5>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Playlists -->
|
|
||||||
<div class="p-4 sm:p-8 bg-white dark:bg-neutral-800 shadow rounded-lg content-center text-center grow">
|
|
||||||
<a href="{{ route('profile.playlists') }}">
|
|
||||||
<div class="inline-block rounded-md text-sky-500">
|
|
||||||
<i class="fa-solid fa-rectangle-list text-3xl"></i>
|
|
||||||
</div>
|
|
||||||
<h5 class="font-medium dark:text-neutral-300">
|
|
||||||
{{ number_format($user->playlists->count()) }} playlists
|
|
||||||
</h5>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-app-layout>
|
|
||||||
@@ -14,28 +14,4 @@
|
|||||||
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
||||||
>{{ __('Delete Account') }}</x-danger-button>
|
>{{ __('Delete Account') }}</x-danger-button>
|
||||||
|
|
||||||
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
|
|
||||||
|
|
||||||
<form method="POST" action="{{ route('profile.delete') }}" class="p-6">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{{ __('Are you sure you want to delete your account?') }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted.') }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
<x-secondary-button x-on:click="$dispatch('close')">
|
|
||||||
{{ __('Cancel') }}
|
|
||||||
</x-secondary-button>
|
|
||||||
|
|
||||||
<x-danger-button class="ml-3">
|
|
||||||
{{ __('Delete Account') }}
|
|
||||||
</x-danger-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
46
resources/views/profile/partials/delete-user-modal.blade.php
Normal file
46
resources/views/profile/partials/delete-user-modal.blade.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
|
||||||
|
<form method="POST" action="{{ route('profile.delete') }}" class="p-6">
|
||||||
|
@csrf
|
||||||
|
@method('delete')
|
||||||
|
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('Are you sure you want to delete your account?') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
@if (is_null($user->password))
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted.') }}
|
||||||
|
</p>
|
||||||
|
@else
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (!is_null($user->password))
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
|
||||||
|
|
||||||
|
<x-text-input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
class="mt-1 block w-3/4"
|
||||||
|
placeholder="{{ __('Password') }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->userDeletion->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-end">
|
||||||
|
<x-secondary-button x-on:click="$dispatch('close')">
|
||||||
|
{{ __('Cancel') }}
|
||||||
|
</x-secondary-button>
|
||||||
|
|
||||||
|
<x-danger-button class="ms-3">
|
||||||
|
{{ __('Delete Account') }}
|
||||||
|
</x-danger-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-modal>
|
||||||
@@ -7,27 +7,43 @@
|
|||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@if ($user->discord_id && is_null($user->password))
|
||||||
|
<div class="p-2 rounded-lg bg-rose-600/80 mt-4">
|
||||||
|
<p class="p-2 text-sm dark:text-gray-200 text-white">
|
||||||
|
{{ __('You currently don\'t have a password set, as you use Discord authentication. You can set a password to be able to login with email & password.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@elseif ($user->discord_id && !is_null($user->password))
|
||||||
|
<div class="p-2 rounded-lg bg-green-600/80 mt-4">
|
||||||
|
<p class="p-2 text-sm dark:text-gray-200 text-white">
|
||||||
|
{{ __('Both Discord and email login are enabled.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
||||||
@csrf
|
@csrf
|
||||||
@method('put')
|
@method('put')
|
||||||
|
|
||||||
|
@if (!(is_null($user->password) && $user->discord_id))
|
||||||
<div>
|
<div>
|
||||||
<x-input-label for="current_password" :value="__('Current Password')" />
|
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
|
||||||
<x-text-input id="current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
<x-text-input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
||||||
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<x-input-label for="password" :value="__('New Password')" />
|
<x-input-label for="update_password_password" :value="__('New Password')" />
|
||||||
<x-text-input id="password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
<x-text-input id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||||
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
|
||||||
<x-text-input id="password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
<x-text-input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||||
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,100 @@
|
|||||||
<section>
|
<section>
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
<i class="fa-brands fa-discord"></i> {{ __('Profile Information') }}
|
{{ __('Profile Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="mt-6 space-y-6">
|
@if ($user->discord_id)
|
||||||
@if (Auth::user()->global_name)
|
<div class="p-2 rounded-lg bg-rose-600/80 mt-4">
|
||||||
<div>
|
<p class="p-2 text-sm dark:text-gray-200 text-white">
|
||||||
<x-input-label for="global_name" :value="__('Display Name')" />
|
{{ __('Changing your name or email will not affect Discord authentication, as your Discord ID has been stored.') }}
|
||||||
<x-text-input id="global_name" name="global_name" type="text" class="mt-1 block w-full" :value="old('global_name', $user->global_name)" required autocomplete="global_name" disabled />
|
</p>
|
||||||
<x-input-error class="mt-2" :messages="$errors->get('global_name')" />
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@else
|
||||||
<div>
|
<div class="p-2 rounded-lg bg-green-600/80 mt-4">
|
||||||
<x-input-label for="username" :value="__('Username')" />
|
<p class="p-2 text-sm dark:text-gray-200 text-white">
|
||||||
<x-text-input id="username" name="username" type="text" class="mt-1 block w-full" :value="old('username', $user->username)" required autocomplete="username" disabled />
|
{{ __('If you want to use Discord authentication, ensure the email addresses match for initial login. After login with Discord, email can be changed.') }}
|
||||||
<x-input-error class="mt-2" :messages="$errors->get('username')" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (!Auth::user()->global_name)
|
|
||||||
<div>
|
|
||||||
<x-input-label for="discriminator" :value="__('Discriminator')" />
|
|
||||||
<x-text-input id="discriminator" name="discriminator" type="text" class="mt-1 block w-full" :value="old('discriminator', $user->discriminator)" required autocomplete="discriminator" disabled />
|
|
||||||
<x-input-error class="mt-2" :messages="$errors->get('discriminator')" />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<x-input-label for="email" :value="__('Email')" />
|
|
||||||
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email ?? __('Unknown'))" required autocomplete="email" disabled />
|
|
||||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
|
||||||
|
|
||||||
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->verified)
|
|
||||||
<div>
|
|
||||||
<p class="text-sm mt-2 text-gray-800 dark:text-gray-200">
|
|
||||||
{{ __('Your email address is unverified.') }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form id="send-verification" method="post" action="{{ route('verification.send') }}">
|
||||||
|
@csrf
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="image" :value="__('Avatar')" />
|
||||||
|
<div class="mt-2 flex items-center gap-4">
|
||||||
|
<img
|
||||||
|
src="{{ $user->getAvatar() }}"
|
||||||
|
alt="{{ $user->name }}"
|
||||||
|
class="h-16 w-16 rounded-full object-cover"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="image"
|
||||||
|
name="image"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
class="block w-full text-sm text-gray-900 dark:text-gray-100
|
||||||
|
file:mr-4 file:rounded-md file:border-0
|
||||||
|
file:bg-rose-600 file:px-4 file:py-2
|
||||||
|
file:text-sm file:font-semibold file:text-white
|
||||||
|
hover:file:bg-rose-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
JPG, PNG, WebP or GIF. Max 8MB. Will be cropped to 128×128.
|
||||||
|
</p>
|
||||||
|
<x-input-error class="mt-2" :messages="$errors->get('image')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="name" :value="__('Name')" />
|
||||||
|
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
|
||||||
|
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="email" />
|
||||||
|
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||||
|
|
||||||
|
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||||
|
<div>
|
||||||
|
<p class="text-sm mt-2 text-gray-800 dark:text-gray-200">
|
||||||
|
{{ __('Your email address is unverified.') }}
|
||||||
|
|
||||||
|
<button form="send-verification" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
|
||||||
|
{{ __('Click here to re-send the verification email.') }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if (session('status') === 'verification-link-sent')
|
||||||
|
<p class="mt-2 font-medium text-sm text-green-600 dark:text-green-400">
|
||||||
|
{{ __('A new verification link has been sent to your email address.') }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||||
|
|
||||||
|
@if (session('status') === 'profile-updated')
|
||||||
|
<p
|
||||||
|
x-data="{ show: true }"
|
||||||
|
x-show="show"
|
||||||
|
x-transition
|
||||||
|
x-init="setTimeout(() => show = false, 2000)"
|
||||||
|
class="text-sm text-gray-600 dark:text-gray-400"
|
||||||
|
>{{ __('Saved.') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -8,13 +8,16 @@
|
|||||||
<!-- Page Content -->
|
<!-- Page Content -->
|
||||||
<main>
|
<main>
|
||||||
@include('user.partials.background')
|
@include('user.partials.background')
|
||||||
<div class="relative max-w-[120rem] mx-auto sm:px-6 lg:px-8 space-y-6 pt-20 mt-[65px] flex flex-row">
|
<div class="relative max-w-[120rem] mx-auto sm:px-6 lg:px-8 space-y-6 pt-20 mt-[65px] mb-14 flex flex-row">
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row">
|
||||||
@include('profile.partials.sidebar')
|
@include('profile.partials.sidebar')
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 mt-8 md:mt-0 space-y-6">
|
||||||
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
||||||
@include('profile.partials.update-profile-information-form')
|
@include('profile.partials.update-profile-information-form')
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
||||||
|
@include('profile.partials.update-password-form')
|
||||||
|
</div>
|
||||||
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
||||||
@include('profile.partials.update-blacklist-form')
|
@include('profile.partials.update-blacklist-form')
|
||||||
</div>
|
</div>
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
|
||||||
@include('profile.partials.delete-user-form')
|
@include('profile.partials.delete-user-form')
|
||||||
</div>
|
</div>
|
||||||
|
@include('profile.partials.delete-user-modal')
|
||||||
</div>
|
</div>
|
||||||
@vite(['resources/js/user-blacklist.js'])
|
@vite(['resources/js/user-blacklist.js'])
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
@endphp
|
@endphp
|
||||||
<p class="text-neutral-800 dark:text-neutral-300">
|
<p class="text-neutral-800 dark:text-neutral-300">
|
||||||
{{ $playlist->user->global_name ?? $playlist->user->username }} • {{ $currentIndex + 1 }}/{{ $episodeCount }} Episodes
|
{{ $playlist->user->name }} • {{ $currentIndex + 1 }}/{{ $episodeCount }} Episodes
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
<div
|
<div
|
||||||
class="overflow-hidden relative max-w-sm min-w-80 mx-auto bg-white/40 shadow-lg ring-1 ring-black/5 rounded-xl flex items-center gap-6 dark:bg-neutral-950/40 backdrop-blur dark:highlight-white/5">
|
class="overflow-hidden relative max-w-sm min-w-80 mx-auto bg-white/40 shadow-lg ring-1 ring-black/5 rounded-xl flex items-center gap-6 dark:bg-neutral-950/40 backdrop-blur dark:highlight-white/5">
|
||||||
@if($user->avatar)
|
<img class="absolute -left-6 w-24 h-24 rounded-full shadow-lg" src="{{ $user->getAvatar() }}">
|
||||||
<img class="absolute -left-6 w-24 h-24 rounded-full shadow-lg"
|
|
||||||
src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ $user->id }}/{{ $user->avatar }}.webp">
|
|
||||||
@else
|
|
||||||
<img class="absolute -left-6 w-24 h-24 rounded-full shadow-lg" src="/images/default-avatar.webp">
|
|
||||||
@endif
|
|
||||||
<div class="flex flex-col py-5 pl-24">
|
<div class="flex flex-col py-5 pl-24">
|
||||||
<strong class="text-slate-900 text-xl font-bold dark:text-slate-200">
|
<strong class="text-slate-900 text-xl font-bold dark:text-slate-200">
|
||||||
{{ $user->global_name ?? $user->username }}
|
{{ $user->name }}
|
||||||
@if ($user->is_patreon)
|
@if ($user->is_patreon)
|
||||||
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i
|
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i
|
||||||
class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a>
|
class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a>
|
||||||
|
|||||||
@@ -7,22 +7,18 @@
|
|||||||
|
|
||||||
<div id="comment-{{ $comment->getKey() }}" class="flex rounded-lg p-1 mb-2 ">
|
<div id="comment-{{ $comment->getKey() }}" class="flex rounded-lg p-1 mb-2 ">
|
||||||
|
|
||||||
<a class="contents" href="{{ route('user.index', ['username' => $comment->commenter->username]) }}">
|
<div class="contents">
|
||||||
@if($comment->commenter->avatar)
|
<img class="w-12 h-12 rounded-lg m-2" src="{{ $comment->commenter->getAvatar() }}" alt="{{ $comment->commenter->name }} Avatar">
|
||||||
<img class="w-12 h-12 rounded-lg m-2" src="https://external-content.duckduckgo.com/iu/?u=https://cdn.discordapp.com/avatars/{{ $comment->commenter->id }}/{{ $comment->commenter->avatar }}.webp" alt="{{ $comment->commenter->global_name ?? $comment->commenter->username }} Avatar">
|
</div>
|
||||||
@else
|
|
||||||
<img class="w-12 h-12 rounded-lg m-2" src="/images/default-avatar.webp" alt="{{ $comment->commenter->global_name ?? $comment->commenter->username }} Avatar">
|
|
||||||
@endif
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="text-gray-800 dark:text-gray-200">
|
<div class="text-gray-800 dark:text-gray-200">
|
||||||
<a href="{{ route('user.index', ['username' => $comment->commenter->username]) }}">
|
<div>
|
||||||
@if($comment->commenter->is_patreon)
|
@if($comment->commenter->is_patreon)
|
||||||
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->global_name ?? $comment->commenter->username }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
|
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->name }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
|
||||||
@else
|
@else
|
||||||
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->global_name ?? $comment->commenter->username }} <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
|
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->name }} <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</div>
|
||||||
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
|
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
|
||||||
|
|
||||||
@if (! Illuminate\Support\Facades\Route::is('profile.comments'))
|
@if (! Illuminate\Support\Facades\Route::is('profile.comments'))
|
||||||
|
|||||||
24
resources/views/vendor/mail/html/button.blade.php
vendored
Normal file
24
resources/views/vendor/mail/html/button.blade.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@props([
|
||||||
|
'url',
|
||||||
|
'color' => 'primary',
|
||||||
|
'align' => 'center',
|
||||||
|
])
|
||||||
|
<table class="action" align="{{ $align }}" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td align="{{ $align }}">
|
||||||
|
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td align="{{ $align }}">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ $url }}" class="button button-{{ $color }}" target="_blank" rel="noopener">{!! $slot !!}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
11
resources/views/vendor/mail/html/footer.blade.php
vendored
Normal file
11
resources/views/vendor/mail/html/footer.blade.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<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>
|
||||||
12
resources/views/vendor/mail/html/header.blade.php
vendored
Normal file
12
resources/views/vendor/mail/html/header.blade.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@props(['url'])
|
||||||
|
<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>
|
||||||
58
resources/views/vendor/mail/html/layout.blade.php
vendored
Normal file
58
resources/views/vendor/mail/html/layout.blade.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!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>
|
||||||
|
<title>{{ config('app.name') }}</title>
|
||||||
|
<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 ?? '' !!}
|
||||||
|
</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" style="border: hidden !important;">
|
||||||
|
<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>
|
||||||
27
resources/views/vendor/mail/html/message.blade.php
vendored
Normal file
27
resources/views/vendor/mail/html/message.blade.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<x-mail::layout>
|
||||||
|
{{-- Header --}}
|
||||||
|
<x-slot:header>
|
||||||
|
<x-mail::header :url="config('app.url')">
|
||||||
|
<img src="{{ url('/images/hs_banner.png') }}" class="logo" alt="{{ config('app.name') }} Banner">
|
||||||
|
</x-mail::header>
|
||||||
|
</x-slot:header>
|
||||||
|
|
||||||
|
{{-- Body --}}
|
||||||
|
{!! $slot !!}
|
||||||
|
|
||||||
|
{{-- Subcopy --}}
|
||||||
|
@isset($subcopy)
|
||||||
|
<x-slot:subcopy>
|
||||||
|
<x-mail::subcopy>
|
||||||
|
{!! $subcopy !!}
|
||||||
|
</x-mail::subcopy>
|
||||||
|
</x-slot:subcopy>
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
{{-- Footer --}}
|
||||||
|
<x-slot:footer>
|
||||||
|
<x-mail::footer>
|
||||||
|
© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights not reserved.') }}
|
||||||
|
</x-mail::footer>
|
||||||
|
</x-slot:footer>
|
||||||
|
</x-mail::layout>
|
||||||
14
resources/views/vendor/mail/html/panel.blade.php
vendored
Normal file
14
resources/views/vendor/mail/html/panel.blade.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
7
resources/views/vendor/mail/html/subcopy.blade.php
vendored
Normal file
7
resources/views/vendor/mail/html/subcopy.blade.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
3
resources/views/vendor/mail/html/table.blade.php
vendored
Normal file
3
resources/views/vendor/mail/html/table.blade.php
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="table">
|
||||||
|
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||||
|
</div>
|
||||||
295
resources/views/vendor/mail/html/themes/default.css
vendored
Normal file
295
resources/views/vendor/mail/html/themes/default.css
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/* 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: #000;
|
||||||
|
color: #fff;
|
||||||
|
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: #fff;
|
||||||
|
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: #000;
|
||||||
|
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: #fff;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo */
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 75px;
|
||||||
|
max-height: 75px;
|
||||||
|
width: 267px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
|
||||||
|
.body {
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
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: #212121;
|
||||||
|
border-color: #e8e5ef;
|
||||||
|
border-radius: 12px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-body a {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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%;
|
||||||
|
float: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-blue,
|
||||||
|
.button-primary {
|
||||||
|
background-color: #be123c;
|
||||||
|
border-bottom: 8px solid #be123c;
|
||||||
|
border-left: 18px solid #be123c;
|
||||||
|
border-right: 18px solid #be123c;
|
||||||
|
border-top: 8px solid #be123c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
1
resources/views/vendor/mail/text/button.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/button.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}: {{ $url }}
|
||||||
1
resources/views/vendor/mail/text/footer.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/footer.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}
|
||||||
1
resources/views/vendor/mail/text/header.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/header.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}: {{ $url }}
|
||||||
9
resources/views/vendor/mail/text/layout.blade.php
vendored
Normal file
9
resources/views/vendor/mail/text/layout.blade.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{!! strip_tags($header ?? '') !!}
|
||||||
|
|
||||||
|
{!! strip_tags($slot) !!}
|
||||||
|
@isset($subcopy)
|
||||||
|
|
||||||
|
{!! strip_tags($subcopy) !!}
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
{!! strip_tags($footer ?? '') !!}
|
||||||
27
resources/views/vendor/mail/text/message.blade.php
vendored
Normal file
27
resources/views/vendor/mail/text/message.blade.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<x-mail::layout>
|
||||||
|
{{-- Header --}}
|
||||||
|
<x-slot:header>
|
||||||
|
<x-mail::header :url="config('app.url')">
|
||||||
|
{{ config('app.name') }}
|
||||||
|
</x-mail::header>
|
||||||
|
</x-slot:header>
|
||||||
|
|
||||||
|
{{-- Body --}}
|
||||||
|
{{ $slot }}
|
||||||
|
|
||||||
|
{{-- Subcopy --}}
|
||||||
|
@isset($subcopy)
|
||||||
|
<x-slot:subcopy>
|
||||||
|
<x-mail::subcopy>
|
||||||
|
{{ $subcopy }}
|
||||||
|
</x-mail::subcopy>
|
||||||
|
</x-slot:subcopy>
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
{{-- Footer --}}
|
||||||
|
<x-slot:footer>
|
||||||
|
<x-mail::footer>
|
||||||
|
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
|
||||||
|
</x-mail::footer>
|
||||||
|
</x-slot:footer>
|
||||||
|
</x-mail::layout>
|
||||||
1
resources/views/vendor/mail/text/panel.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/panel.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}
|
||||||
1
resources/views/vendor/mail/text/subcopy.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/subcopy.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}
|
||||||
1
resources/views/vendor/mail/text/table.blade.php
vendored
Normal file
1
resources/views/vendor/mail/text/table.blade.php
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ $slot }}
|
||||||
64
routes/auth.php
Normal file
64
routes/auth.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||||
|
use App\Http\Controllers\Auth\ConfirmablePasswordController;
|
||||||
|
use App\Http\Controllers\Auth\DiscordAuthController;
|
||||||
|
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
|
||||||
|
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
||||||
|
use App\Http\Controllers\Auth\NewPasswordController;
|
||||||
|
use App\Http\Controllers\Auth\PasswordController;
|
||||||
|
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||||
|
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||||
|
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware('guest')->group(function () {
|
||||||
|
Route::post('register', [RegisteredUserController::class, 'store'])
|
||||||
|
->name('register');
|
||||||
|
|
||||||
|
Route::get('login', [AuthenticatedSessionController::class, 'create'])
|
||||||
|
->name('login');
|
||||||
|
|
||||||
|
Route::post('login', [AuthenticatedSessionController::class, 'store']);
|
||||||
|
|
||||||
|
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
|
||||||
|
->name('password.request');
|
||||||
|
|
||||||
|
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
|
||||||
|
->name('password.email');
|
||||||
|
|
||||||
|
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
|
||||||
|
->name('password.reset');
|
||||||
|
|
||||||
|
Route::post('reset-password', [NewPasswordController::class, 'store'])
|
||||||
|
->name('password.store');
|
||||||
|
|
||||||
|
// Discord OAuth (Socialite)
|
||||||
|
Route::get('/auth/discord', [DiscordAuthController::class, 'redirect'])
|
||||||
|
->name('discord.login');
|
||||||
|
|
||||||
|
Route::get('/auth/discord/callback', [DiscordAuthController::class, 'callback']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
Route::get('verify-email', EmailVerificationPromptController::class)
|
||||||
|
->name('verification.notice');
|
||||||
|
|
||||||
|
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
|
||||||
|
->middleware(['signed', 'throttle:6,1'])
|
||||||
|
->name('verification.verify');
|
||||||
|
|
||||||
|
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
|
||||||
|
->middleware('throttle:6,1')
|
||||||
|
->name('verification.send');
|
||||||
|
|
||||||
|
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
|
||||||
|
->name('password.confirm');
|
||||||
|
|
||||||
|
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
|
||||||
|
|
||||||
|
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
|
||||||
|
|
||||||
|
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
|
||||||
|
->name('logout');
|
||||||
|
});
|
||||||
@@ -63,13 +63,14 @@ Route::middleware('auth')->group(function () {
|
|||||||
Route::get('/user/notifications', [App\Http\Controllers\NotificationController::class, 'index'])->name('profile.notifications');
|
Route::get('/user/notifications', [App\Http\Controllers\NotificationController::class, 'index'])->name('profile.notifications');
|
||||||
Route::delete('/user/notifications', [App\Http\Controllers\NotificationController::class, 'delete'])->name('profile.notifications.delete');
|
Route::delete('/user/notifications', [App\Http\Controllers\NotificationController::class, 'delete'])->name('profile.notifications.delete');
|
||||||
|
|
||||||
|
// User Profile Actions
|
||||||
Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings');
|
Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings');
|
||||||
|
Route::patch('/user/settings', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
Route::delete('/user/delete', [ProfileController::class, 'destroy'])->name('profile.delete');
|
||||||
Route::post('/user/settings', [ProfileController::class, 'saveSettings'])->name('profile.settings.save');
|
Route::post('/user/settings', [ProfileController::class, 'saveSettings'])->name('profile.settings.save');
|
||||||
Route::get('/user/blacklist', [UserApiController::class, 'getBlacklist'])->name('profile.blacklist');
|
Route::get('/user/blacklist', [UserApiController::class, 'getBlacklist'])->name('profile.blacklist');
|
||||||
Route::post('/user/blacklist', [ProfileController::class, 'saveBlacklist'])->name('profile.blacklist.save');
|
Route::post('/user/blacklist', [ProfileController::class, 'saveBlacklist'])->name('profile.blacklist.save');
|
||||||
|
|
||||||
Route::post('/user/delete', [UserController::class, 'delete'])->name('profile.delete');
|
|
||||||
|
|
||||||
// Playlist Routes for User Page
|
// Playlist Routes for User Page
|
||||||
Route::get('/user/playlists', [PlaylistController::class, 'playlists'])->name('profile.playlists');
|
Route::get('/user/playlists', [PlaylistController::class, 'playlists'])->name('profile.playlists');
|
||||||
Route::get('/user/playlist/{playlist_id}', [PlaylistController::class, 'showPlaylist'])->name('profile.playlist.show');
|
Route::get('/user/playlist/{playlist_id}', [PlaylistController::class, 'showPlaylist'])->name('profile.playlist.show');
|
||||||
@@ -85,8 +86,6 @@ Route::middleware('auth')->group(function () {
|
|||||||
Route::get('/download-search', [HomeController::class, 'downloadSearch'])->name('download.search');
|
Route::get('/download-search', [HomeController::class, 'downloadSearch'])->name('download.search');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/user/{username}', [UserController::class, 'index'])->name('user.index');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|---------------------------------------------------------------------------------
|
|---------------------------------------------------------------------------------
|
||||||
| Admin Pages
|
| Admin Pages
|
||||||
@@ -136,3 +135,5 @@ Route::group(['middleware' => ['auth', 'auth.admin']], function () {
|
|||||||
Route::post('/admin/add-new-subtitle', [App\Http\Controllers\Admin\SubtitleController::class, 'store'])->name('admin.add.new.subtitle');
|
Route::post('/admin/add-new-subtitle', [App\Http\Controllers\Admin\SubtitleController::class, 'store'])->name('admin.add.new.subtitle');
|
||||||
Route::post('/admin/update-subtitles', [App\Http\Controllers\Admin\SubtitleController::class, 'update'])->name('admin.update.subtitles');
|
Route::post('/admin/update-subtitles', [App\Http\Controllers\Admin\SubtitleController::class, 'update'])->name('admin.update.subtitles');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
require __DIR__.'/auth.php';
|
||||||
|
|||||||
54
tests/Feature/Auth/AuthenticationTest.php
Normal file
54
tests/Feature/Auth/AuthenticationTest.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AuthenticationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_login_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/login');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_users_can_authenticate_using_the_login_screen(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->post('/login', [
|
||||||
|
'email' => $user->email,
|
||||||
|
'password' => 'password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertAuthenticated();
|
||||||
|
$response->assertRedirect(route('dashboard', absolute: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_users_can_not_authenticate_with_invalid_password(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->post('/login', [
|
||||||
|
'email' => $user->email,
|
||||||
|
'password' => 'wrong-password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertGuest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_users_can_logout(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->post('/logout');
|
||||||
|
|
||||||
|
$this->assertGuest();
|
||||||
|
$response->assertRedirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
58
tests/Feature/Auth/EmailVerificationTest.php
Normal file
58
tests/Feature/Auth/EmailVerificationTest.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\Verified;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class EmailVerificationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_email_verification_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/verify-email');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_email_can_be_verified(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
$verificationUrl = URL::temporarySignedRoute(
|
||||||
|
'verification.verify',
|
||||||
|
now()->addMinutes(60),
|
||||||
|
['id' => $user->id, 'hash' => sha1($user->email)]
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get($verificationUrl);
|
||||||
|
|
||||||
|
Event::assertDispatched(Verified::class);
|
||||||
|
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
||||||
|
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_email_is_not_verified_with_invalid_hash(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->unverified()->create();
|
||||||
|
|
||||||
|
$verificationUrl = URL::temporarySignedRoute(
|
||||||
|
'verification.verify',
|
||||||
|
now()->addMinutes(60),
|
||||||
|
['id' => $user->id, 'hash' => sha1('wrong-email')]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->actingAs($user)->get($verificationUrl);
|
||||||
|
|
||||||
|
$this->assertFalse($user->fresh()->hasVerifiedEmail());
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/Feature/Auth/PasswordConfirmationTest.php
Normal file
44
tests/Feature/Auth/PasswordConfirmationTest.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PasswordConfirmationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_confirm_password_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/confirm-password');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_password_can_be_confirmed(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->post('/confirm-password', [
|
||||||
|
'password' => 'password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect();
|
||||||
|
$response->assertSessionHasNoErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_password_is_not_confirmed_with_invalid_password(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->post('/confirm-password', [
|
||||||
|
'password' => 'wrong-password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSessionHasErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
73
tests/Feature/Auth/PasswordResetTest.php
Normal file
73
tests/Feature/Auth/PasswordResetTest.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Notifications\ResetPassword;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PasswordResetTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_reset_password_link_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/forgot-password');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_link_can_be_requested(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->post('/forgot-password', ['email' => $user->email]);
|
||||||
|
|
||||||
|
Notification::assertSentTo($user, ResetPassword::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reset_password_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->post('/forgot-password', ['email' => $user->email]);
|
||||||
|
|
||||||
|
Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
|
||||||
|
$response = $this->get('/reset-password/'.$notification->token);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_password_can_be_reset_with_valid_token(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->post('/forgot-password', ['email' => $user->email]);
|
||||||
|
|
||||||
|
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
|
||||||
|
$response = $this->post('/reset-password', [
|
||||||
|
'token' => $notification->token,
|
||||||
|
'email' => $user->email,
|
||||||
|
'password' => 'password',
|
||||||
|
'password_confirmation' => 'password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
51
tests/Feature/Auth/PasswordUpdateTest.php
Normal file
51
tests/Feature/Auth/PasswordUpdateTest.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PasswordUpdateTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_password_can_be_updated(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->from('/profile')
|
||||||
|
->put('/password', [
|
||||||
|
'current_password' => 'password',
|
||||||
|
'password' => 'new-password',
|
||||||
|
'password_confirmation' => 'new-password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect('/profile');
|
||||||
|
|
||||||
|
$this->assertTrue(Hash::check('new-password', $user->refresh()->password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_correct_password_must_be_provided_to_update_password(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->from('/profile')
|
||||||
|
->put('/password', [
|
||||||
|
'current_password' => 'wrong-password',
|
||||||
|
'password' => 'new-password',
|
||||||
|
'password_confirmation' => 'new-password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasErrorsIn('updatePassword', 'current_password')
|
||||||
|
->assertRedirect('/profile');
|
||||||
|
}
|
||||||
|
}
|
||||||
31
tests/Feature/Auth/RegistrationTest.php
Normal file
31
tests/Feature/Auth/RegistrationTest.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class RegistrationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_registration_screen_can_be_rendered(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/register');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_new_users_can_register(): void
|
||||||
|
{
|
||||||
|
$response = $this->post('/register', [
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
'password' => 'password',
|
||||||
|
'password_confirmation' => 'password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertAuthenticated();
|
||||||
|
$response->assertRedirect(route('dashboard', absolute: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
99
tests/Feature/ProfileTest.php
Normal file
99
tests/Feature/ProfileTest.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ProfileTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_profile_page_is_displayed(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->get('/profile');
|
||||||
|
|
||||||
|
$response->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_profile_information_can_be_updated(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->patch('/profile', [
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect('/profile');
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
|
||||||
|
$this->assertSame('Test User', $user->name);
|
||||||
|
$this->assertSame('test@example.com', $user->email);
|
||||||
|
$this->assertNull($user->email_verified_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->patch('/profile', [
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => $user->email,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect('/profile');
|
||||||
|
|
||||||
|
$this->assertNotNull($user->refresh()->email_verified_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_can_delete_their_account(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->delete('/profile', [
|
||||||
|
'password' => 'password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasNoErrors()
|
||||||
|
->assertRedirect('/');
|
||||||
|
|
||||||
|
$this->assertGuest();
|
||||||
|
$this->assertNull($user->fresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_correct_password_must_be_provided_to_delete_account(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($user)
|
||||||
|
->from('/profile')
|
||||||
|
->delete('/profile', [
|
||||||
|
'password' => 'wrong-password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSessionHasErrorsIn('userDeletion', 'password')
|
||||||
|
->assertRedirect('/profile');
|
||||||
|
|
||||||
|
$this->assertNotNull($user->fresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user