Auth: convert auth views and verification email to Nova layout

This commit is contained in:
2026-02-21 07:37:08 +01:00
parent 93b009d42a
commit 795c7a835f
117 changed files with 5385 additions and 1291 deletions

View File

@@ -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');
}
};

View File

@@ -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) {
//
});
}
};

View File

@@ -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]);
}
}
};

View File

@@ -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');
}
};

View File

@@ -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');
}
};

View File

@@ -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');
}
});
}
};

View File

@@ -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;
}
}
}
};