Auth: convert auth views and verification email to Nova layout
This commit is contained in:
@@ -23,7 +23,17 @@ class UserFactory extends Factory
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$username = Str::lower(Str::slug(fake()->unique()->userName(), '-'));
|
||||
$username = preg_replace('/[^a-z0-9_-]/', '-', $username) ?: 'user';
|
||||
$username = substr(trim($username, '-_'), 0, 20);
|
||||
if ($username === '') {
|
||||
$username = 'user' . fake()->unique()->numberBetween(100, 99999);
|
||||
}
|
||||
|
||||
return [
|
||||
'username' => $username,
|
||||
'username_changed_at' => now()->subDays(120),
|
||||
'onboarding_step' => 'complete',
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('forum_post_reports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('post_id')->constrained('forum_posts')->cascadeOnDelete();
|
||||
$table->foreignId('thread_id')->constrained('forum_threads')->cascadeOnDelete();
|
||||
$table->foreignId('reporter_user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->string('status', 20)->default('open');
|
||||
$table->string('reason', 500)->nullable();
|
||||
$table->string('source_url', 1024)->nullable();
|
||||
$table->timestamp('reported_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['post_id', 'reporter_user_id'], 'forum_post_reports_unique_reporter_per_post');
|
||||
$table->index(['thread_id', 'status']);
|
||||
$table->index('reported_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('forum_post_reports');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasColumn('users', 'username')) {
|
||||
$this->normalizeExistingUsernames();
|
||||
}
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'username')) {
|
||||
$table->string('username', 20)->nullable()->change();
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('users', 'username_changed_at')) {
|
||||
$table->timestamp('username_changed_at')->nullable()->after('username');
|
||||
}
|
||||
});
|
||||
|
||||
$sm = Schema::getConnection()->getSchemaBuilder();
|
||||
$indexes = $sm->getIndexes('users');
|
||||
$hasUsernameUnique = collect($indexes)->contains(function ($index): bool {
|
||||
$columns = array_map('strtolower', (array) ($index['columns'] ?? []));
|
||||
return (bool) ($index['unique'] ?? false) && $columns === ['username'];
|
||||
});
|
||||
|
||||
if (! $hasUsernameUnique && Schema::hasColumn('users', 'username')) {
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->unique('username', 'users_username_unique');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'username_changed_at')) {
|
||||
$table->dropColumn('username_changed_at');
|
||||
}
|
||||
if (Schema::hasColumn('users', 'username')) {
|
||||
$table->string('username', 80)->nullable()->change();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function normalizeExistingUsernames(): void
|
||||
{
|
||||
$rows = DB::table('users')
|
||||
->select('id', 'username')
|
||||
->whereNotNull('username')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
if ($rows->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resolved = [];
|
||||
$used = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$raw = strtolower(trim((string) $row->username));
|
||||
$base = preg_replace('/[^a-z0-9_-]+/', '_', $raw) ?? '';
|
||||
$base = trim($base, '_-');
|
||||
if ($base === '') {
|
||||
$base = 'user' . (int) $row->id;
|
||||
}
|
||||
|
||||
$base = substr($base, 0, 20);
|
||||
if ($base === '') {
|
||||
$base = 'user';
|
||||
}
|
||||
|
||||
$candidate = $base;
|
||||
$suffix = 1;
|
||||
|
||||
while (isset($used[$candidate])) {
|
||||
$suffixValue = (string) $suffix;
|
||||
$prefixLen = max(1, 20 - strlen($suffixValue));
|
||||
$candidate = substr($base, 0, $prefixLen) . $suffixValue;
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
$used[$candidate] = true;
|
||||
$resolved[(int) $row->id] = $candidate;
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
DB::table('users')
|
||||
->where('id', (int) $row->id)
|
||||
->update(['username' => 'tmpu' . (int) $row->id]);
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$final = $resolved[(int) $row->id] ?? null;
|
||||
if ($final === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('users')
|
||||
->where('id', (int) $row->id)
|
||||
->update(['username' => $final]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('username_history', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->string('old_username', 20);
|
||||
$table->timestamp('changed_at');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_id', 'changed_at']);
|
||||
$table->index('old_username');
|
||||
});
|
||||
|
||||
Schema::create('username_redirects', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('old_username', 20)->unique();
|
||||
$table->string('new_username', 20);
|
||||
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('new_username');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('username_redirects');
|
||||
Schema::dropIfExists('username_history');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('username_approval_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('requested_username', 20);
|
||||
$table->string('context', 32)->default('unknown');
|
||||
$table->string('similar_to', 20)->nullable();
|
||||
$table->string('status', 20)->default('pending');
|
||||
$table->json('payload')->nullable();
|
||||
$table->foreignId('reviewed_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('reviewed_at')->nullable();
|
||||
$table->text('review_note')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['status', 'created_at']);
|
||||
$table->index(['requested_username', 'status']);
|
||||
$table->index(['user_id', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('username_approval_requests');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('users', 'email_verified_at')) {
|
||||
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('users', 'onboarding_step')) {
|
||||
$table->enum('onboarding_step', ['email', 'verified', 'password', 'username', 'complete'])
|
||||
->nullable()
|
||||
->after('email_verified_at');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('users', 'username_changed_at')) {
|
||||
$table->timestamp('username_changed_at')->nullable()->after('username');
|
||||
}
|
||||
});
|
||||
|
||||
if (! Schema::hasTable('user_verification_tokens')) {
|
||||
Schema::create('user_verification_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->string('token', 128)->unique();
|
||||
$table->timestamp('expires_at');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_id', 'expires_at']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasTable('user_verification_tokens')) {
|
||||
Schema::drop('user_verification_tokens');
|
||||
}
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'onboarding_step')) {
|
||||
$table->dropColumn('onboarding_step');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumn('users', 'username')) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('users')
|
||||
->whereNotNull('username')
|
||||
->update(['username' => DB::raw('LOWER(username)')]);
|
||||
|
||||
$driver = DB::getDriverName();
|
||||
|
||||
try {
|
||||
if ($driver === 'mysql') {
|
||||
DB::statement(
|
||||
'ALTER TABLE users ADD CONSTRAINT users_username_lowercase_check CHECK (username IS NULL OR BINARY username = LOWER(username))'
|
||||
);
|
||||
} elseif ($driver === 'pgsql') {
|
||||
DB::statement(
|
||||
'ALTER TABLE users ADD CONSTRAINT users_username_lowercase_check CHECK (username IS NULL OR username = LOWER(username))'
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if (! str_contains(strtolower($e->getMessage()), 'already exists')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$driver = DB::getDriverName();
|
||||
|
||||
try {
|
||||
if ($driver === 'mysql') {
|
||||
DB::statement('ALTER TABLE users DROP CHECK users_username_lowercase_check');
|
||||
} elseif ($driver === 'pgsql') {
|
||||
DB::statement('ALTER TABLE users DROP CONSTRAINT IF EXISTS users_username_lowercase_check');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if (! str_contains(strtolower($e->getMessage()), 'check') && ! str_contains(strtolower($e->getMessage()), 'constraint')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user