more fixes

This commit is contained in:
2026-03-12 07:22:38 +01:00
parent 547215cbe8
commit 4f576ceb04
226 changed files with 14380 additions and 4453 deletions

View File

@@ -12,11 +12,15 @@ return new class extends Migration {
$table->foreignId('artwork_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->index();
$table->binary('ip', 16);
$table->string('user_agent')->nullable();
// Legacy binary IP is kept for existing analytics/tests compatibility.
$table->binary('ip', 16)->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('referer')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index('created_at');
$table->index(['artwork_id', 'created_at'], 'idx_downloads_artwork');
});
}

View File

@@ -0,0 +1,29 @@
<?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
{
return;
Schema::table('contents', function (Blueprint $table) {
$table->text('grid_data')->nullable()->after('views');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('contents', function (Blueprint $table) {
$table->dropColumn('grid_data');
});
}
};

View File

@@ -0,0 +1,54 @@
<?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('artwork_downloads', function (Blueprint $table) {
if (! Schema::hasColumn('artwork_downloads', 'ip_address')) {
$table->string('ip_address', 45)->nullable()->after('ip');
}
if (! Schema::hasColumn('artwork_downloads', 'referer')) {
$table->text('referer')->nullable()->after('user_agent');
}
if (! Schema::hasColumn('artwork_downloads', 'created_at')) {
$table->timestamp('created_at')->useCurrent();
}
});
try {
Schema::table('artwork_downloads', function (Blueprint $table) {
$table->index('created_at', 'artwork_downloads_created_at_idx');
});
} catch (\Throwable) {
// Index may already exist depending on historical migration state.
}
}
public function down(): void
{
try {
Schema::table('artwork_downloads', function (Blueprint $table) {
$table->dropIndex('artwork_downloads_created_at_idx');
});
} catch (\Throwable) {
// Ignore when index is absent.
}
Schema::table('artwork_downloads', function (Blueprint $table) {
if (Schema::hasColumn('artwork_downloads', 'ip_address')) {
$table->dropColumn('ip_address');
}
if (Schema::hasColumn('artwork_downloads', 'referer')) {
$table->dropColumn('referer');
}
});
}
};

View File

@@ -0,0 +1,41 @@
<?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): void {
if (! Schema::hasColumn('users', 'cover_hash')) {
$table->string('cover_hash', 64)->nullable()->after('last_visit_at');
}
if (! Schema::hasColumn('users', 'cover_ext')) {
$table->string('cover_ext', 10)->nullable()->after('cover_hash');
}
if (! Schema::hasColumn('users', 'cover_position')) {
$table->unsignedTinyInteger('cover_position')->default(50)->after('cover_ext');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
if (Schema::hasColumn('users', 'cover_position')) {
$table->dropColumn('cover_position');
}
if (Schema::hasColumn('users', 'cover_ext')) {
$table->dropColumn('cover_ext');
}
if (Schema::hasColumn('users', 'cover_hash')) {
$table->dropColumn('cover_hash');
}
});
}
};

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
if (!Schema::hasTable('user_profiles')) {
return;
}
Schema::table('user_profiles', function (Blueprint $table) {
if (!Schema::hasColumn('user_profiles', 'email_notifications')) {
$table->boolean('email_notifications')->default(true)->after('auto_post_upload');
}
if (!Schema::hasColumn('user_profiles', 'upload_notifications')) {
$table->boolean('upload_notifications')->default(true)->after('email_notifications');
}
if (!Schema::hasColumn('user_profiles', 'follower_notifications')) {
$table->boolean('follower_notifications')->default(true)->after('upload_notifications');
}
if (!Schema::hasColumn('user_profiles', 'comment_notifications')) {
$table->boolean('comment_notifications')->default(true)->after('follower_notifications');
}
if (!Schema::hasColumn('user_profiles', 'newsletter')) {
$table->boolean('newsletter')->default(false)->after('comment_notifications');
}
});
}
public function down(): void
{
if (!Schema::hasTable('user_profiles')) {
return;
}
Schema::table('user_profiles', function (Blueprint $table) {
foreach (['newsletter', 'comment_notifications', 'follower_notifications', 'upload_notifications', 'email_notifications'] as $column) {
if (Schema::hasColumn('user_profiles', $column)) {
$table->dropColumn($column);
}
}
});
}
};

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::table('users', function (Blueprint $table) {
if (! Schema::hasColumn('users', 'last_username_change_at')) {
$table->timestamp('last_username_change_at')->nullable()->after('username_changed_at');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'last_username_change_at')) {
$table->dropColumn('last_username_change_at');
}
});
}
};

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
{
if (Schema::hasTable('email_changes')) {
return;
}
Schema::create('email_changes', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->string('new_email', 255);
$table->string('verification_code', 128);
$table->timestamp('expires_at');
$table->timestamp('used_at')->nullable();
$table->timestamps();
$table->index(['user_id', 'expires_at']);
$table->index(['user_id', 'created_at']);
$table->index('new_email');
});
}
public function down(): void
{
Schema::dropIfExists('email_changes');
}
};

View File

@@ -0,0 +1,98 @@
<?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::hasTable('stories')) {
Schema::table('stories', function (Blueprint $table): void {
if (! Schema::hasColumn('stories', 'creator_id')) {
$table->foreignId('creator_id')->nullable()->after('id')->constrained('users')->nullOnDelete();
}
if (! Schema::hasColumn('stories', 'story_type')) {
$table->enum('story_type', [
'creator_story',
'tutorial',
'interview',
'project_breakdown',
'announcement',
'resource',
])->default('creator_story')->after('content');
}
if (! Schema::hasColumn('stories', 'reading_time')) {
$table->unsignedInteger('reading_time')->default(1)->after('story_type');
}
if (! Schema::hasColumn('stories', 'likes_count')) {
$table->unsignedInteger('likes_count')->default(0)->after('views');
}
if (! Schema::hasColumn('stories', 'comments_count')) {
$table->unsignedInteger('comments_count')->default(0)->after('likes_count');
}
});
if (Schema::hasColumn('stories', 'author_id') && Schema::hasTable('stories_authors')) {
DB::statement(<<<'SQL'
UPDATE stories s
INNER JOIN stories_authors sa ON sa.id = s.author_id
SET s.creator_id = sa.user_id
WHERE s.creator_id IS NULL
AND sa.user_id IS NOT NULL
SQL);
}
}
if (! Schema::hasTable('story_views')) {
Schema::create('story_views', function (Blueprint $table): void {
$table->id();
$table->foreignId('story_id')->constrained('stories')->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['story_id', 'created_at']);
$table->index(['user_id', 'created_at']);
});
}
if (! Schema::hasTable('story_likes')) {
Schema::create('story_likes', function (Blueprint $table): void {
$table->id();
$table->foreignId('story_id')->constrained('stories')->cascadeOnDelete();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->timestamp('created_at')->useCurrent();
$table->unique(['story_id', 'user_id']);
$table->index(['story_id', 'created_at']);
});
}
}
public function down(): void
{
Schema::dropIfExists('story_likes');
Schema::dropIfExists('story_views');
if (Schema::hasTable('stories')) {
Schema::table('stories', function (Blueprint $table): void {
if (Schema::hasColumn('stories', 'creator_id')) {
$table->dropConstrainedForeignId('creator_id');
}
foreach (['story_type', 'reading_time', 'likes_count', 'comments_count'] as $column) {
if (Schema::hasColumn('stories', $column)) {
$table->dropColumn($column);
}
}
});
}
}
};

View File

@@ -0,0 +1,62 @@
<?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::hasTable('story_tags')) {
Schema::create('story_tags', function (Blueprint $table): void {
$table->id();
$table->string('name', 120)->unique();
$table->string('slug', 140)->unique();
$table->timestamps();
});
}
if (! Schema::hasTable('relation_story_tags')) {
Schema::create('relation_story_tags', function (Blueprint $table): void {
$table->foreignId('story_id')->constrained('stories')->cascadeOnDelete();
$table->foreignId('tag_id')->constrained('story_tags')->cascadeOnDelete();
$table->primary(['story_id', 'tag_id']);
});
}
if (Schema::hasTable('stories_tags')) {
$legacyTags = DB::table('stories_tags')->get();
foreach ($legacyTags as $legacyTag) {
DB::table('story_tags')->insertOrIgnore([
'name' => (string) $legacyTag->name,
'slug' => (string) $legacyTag->slug,
'created_at' => $legacyTag->created_at ?? now(),
'updated_at' => $legacyTag->updated_at ?? now(),
]);
}
}
if (Schema::hasTable('stories_tag_relation')) {
$legacyRelation = DB::table('stories_tag_relation as relation')
->join('stories_tags as legacy_tag', 'legacy_tag.id', '=', 'relation.tag_id')
->join('story_tags as new_tag', 'new_tag.slug', '=', 'legacy_tag.slug')
->get(['relation.story_id', 'new_tag.id as tag_id']);
foreach ($legacyRelation as $pair) {
DB::table('relation_story_tags')->insertOrIgnore([
'story_id' => (int) $pair->story_id,
'tag_id' => (int) $pair->tag_id,
]);
}
}
}
public function down(): void
{
Schema::dropIfExists('relation_story_tags');
Schema::dropIfExists('story_tags');
}
};

View File

@@ -0,0 +1,30 @@
<?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::hasTable('stories') || ! Schema::hasColumn('stories', 'status')) {
return;
}
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE stories MODIFY status ENUM('draft','published','scheduled','archived') NOT NULL DEFAULT 'draft'");
}
}
public function down(): void
{
if (! Schema::hasTable('stories') || ! Schema::hasColumn('stories', 'status')) {
return;
}
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE stories MODIFY status ENUM('draft','published') NOT NULL DEFAULT 'draft'");
}
}
};

View File

@@ -0,0 +1,30 @@
<?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::hasTable('reports') || ! Schema::hasColumn('reports', 'target_type')) {
return;
}
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE reports MODIFY target_type ENUM('message','conversation','user','story') NOT NULL");
}
}
public function down(): void
{
if (! Schema::hasTable('reports') || ! Schema::hasColumn('reports', 'target_type')) {
return;
}
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE reports MODIFY target_type ENUM('message','conversation','user') NOT NULL");
}
}
};

View File

@@ -0,0 +1,96 @@
<?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::hasTable('stories')) {
return;
}
if (DB::getDriverName() === 'mysql' && Schema::hasColumn('stories', 'status')) {
DB::statement("ALTER TABLE stories MODIFY status ENUM('draft','pending_review','published','scheduled','archived','rejected') NOT NULL DEFAULT 'draft'");
}
Schema::table('stories', function (Blueprint $table): void {
if (! Schema::hasColumn('stories', 'scheduled_for')) {
$table->timestamp('scheduled_for')->nullable()->after('published_at');
}
if (! Schema::hasColumn('stories', 'meta_title')) {
$table->string('meta_title', 255)->nullable()->after('reading_time');
}
if (! Schema::hasColumn('stories', 'meta_description')) {
$table->string('meta_description', 300)->nullable()->after('meta_title');
}
if (! Schema::hasColumn('stories', 'canonical_url')) {
$table->string('canonical_url', 500)->nullable()->after('meta_description');
}
if (! Schema::hasColumn('stories', 'og_image')) {
$table->string('og_image', 500)->nullable()->after('canonical_url');
}
if (! Schema::hasColumn('stories', 'submitted_for_review_at')) {
$table->timestamp('submitted_for_review_at')->nullable()->after('scheduled_for');
}
if (! Schema::hasColumn('stories', 'reviewed_at')) {
$table->timestamp('reviewed_at')->nullable()->after('submitted_for_review_at');
}
if (! Schema::hasColumn('stories', 'reviewed_by_id')) {
$table->foreignId('reviewed_by_id')->nullable()->after('reviewed_at')->constrained('users')->nullOnDelete();
}
if (! Schema::hasColumn('stories', 'rejected_reason')) {
$table->text('rejected_reason')->nullable()->after('reviewed_by_id');
}
$table->index(['status', 'submitted_for_review_at'], 'idx_stories_review_queue');
$table->index(['creator_id', 'status', 'updated_at'], 'idx_stories_creator_status_updated');
});
}
public function down(): void
{
if (! Schema::hasTable('stories')) {
return;
}
Schema::table('stories', function (Blueprint $table): void {
if (Schema::hasColumn('stories', 'reviewed_by_id')) {
$table->dropConstrainedForeignId('reviewed_by_id');
}
foreach ([
'scheduled_for',
'meta_title',
'meta_description',
'canonical_url',
'og_image',
'submitted_for_review_at',
'reviewed_at',
'rejected_reason',
] as $column) {
if (Schema::hasColumn('stories', $column)) {
$table->dropColumn($column);
}
}
$table->dropIndex('idx_stories_review_queue');
$table->dropIndex('idx_stories_creator_status_updated');
});
if (DB::getDriverName() === 'mysql' && Schema::hasColumn('stories', 'status')) {
DB::statement("ALTER TABLE stories MODIFY status ENUM('draft','published','scheduled','archived') NOT NULL DEFAULT 'draft'");
}
}
};