UI: portal/header refinements, board index UX, and user settings

This commit is contained in:
Micha
2026-01-01 19:54:02 +01:00
parent f83748cc76
commit 8604cdf95d
26 changed files with 2065 additions and 227 deletions

View File

@@ -0,0 +1,50 @@
<?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('forums', function (Blueprint $table) {
$table->softDeletes();
$table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
$table->index(['deleted_at', 'deleted_by'], 'idx_forums_deleted_meta');
});
Schema::table('threads', function (Blueprint $table) {
$table->softDeletes();
$table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
$table->index(['deleted_at', 'deleted_by'], 'idx_threads_deleted_meta');
});
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes();
$table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
$table->index(['deleted_at', 'deleted_by'], 'idx_posts_deleted_meta');
});
}
public function down(): void
{
Schema::table('forums', function (Blueprint $table) {
$table->dropIndex('idx_forums_deleted_meta');
$table->dropConstrainedForeignId('deleted_by');
$table->dropSoftDeletes();
});
Schema::table('threads', function (Blueprint $table) {
$table->dropIndex('idx_threads_deleted_meta');
$table->dropConstrainedForeignId('deleted_by');
$table->dropSoftDeletes();
});
Schema::table('posts', function (Blueprint $table) {
$table->dropIndex('idx_posts_deleted_meta');
$table->dropConstrainedForeignId('deleted_by');
$table->dropSoftDeletes();
});
}
};

View File

@@ -0,0 +1,52 @@
<?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
{
DB::table('forums')
->where('type', 'forum')
->whereNull('parent_id')
->update(['type' => 'category']);
$driver = Schema::getConnection()->getDriverName();
if ($driver === 'mysql') {
DB::statement('DROP TRIGGER IF EXISTS trg_forums_parent_insert');
DB::statement('DROP TRIGGER IF EXISTS trg_forums_parent_update');
DB::statement(<<<'SQL'
CREATE TRIGGER trg_forums_parent_insert
BEFORE INSERT ON forums
FOR EACH ROW
BEGIN
IF NEW.type = 'forum' AND NEW.parent_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Forums must belong to a category.';
END IF;
END
SQL);
DB::statement(<<<'SQL'
CREATE TRIGGER trg_forums_parent_update
BEFORE UPDATE ON forums
FOR EACH ROW
BEGIN
IF NEW.type = 'forum' AND NEW.parent_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Forums must belong to a category.';
END IF;
END
SQL);
}
}
public function down(): void
{
$driver = Schema::getConnection()->getDriverName();
if ($driver === 'mysql') {
DB::statement('DROP TRIGGER IF EXISTS trg_forums_parent_insert');
DB::statement('DROP TRIGGER IF EXISTS trg_forums_parent_update');
}
}
};

View File

@@ -0,0 +1,25 @@
<?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('user_settings', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('key');
$table->json('value');
$table->timestamps();
$table->unique(['user_id', 'key'], 'uniq_user_settings_user_key');
});
}
public function down(): void
{
Schema::dropIfExists('user_settings');
}
};

View File

@@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder
RoleSeeder::class,
UserSeeder::class,
ForumSeeder::class,
ThreadSeeder::class,
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Database\Seeders;
use App\Models\Forum;
use App\Models\Thread;
use App\Models\User;
use Faker\Factory as FakerFactory;
use Illuminate\Database\Seeder;
class ThreadSeeder extends Seeder
{
public function run(): void
{
$faker = FakerFactory::create();
$users = User::all();
$forums = Forum::where('type', 'forum')->get();
if ($users->isEmpty() || $forums->isEmpty()) {
return;
}
foreach ($forums as $forum) {
$threadCount = $faker->numberBetween(2, 8);
for ($i = 0; $i < $threadCount; $i += 1) {
$author = $users->random();
Thread::create([
'forum_id' => $forum->id,
'user_id' => $author->id,
'title' => ucfirst($faker->words($faker->numberBetween(3, 6), true)),
'body' => $faker->paragraphs($faker->numberBetween(2, 4), true),
]);
}
}
}
}