This commit is contained in:
2026-03-20 21:17:26 +01:00
parent 1a62fcb81d
commit 29c3ff8572
229 changed files with 13147 additions and 2577 deletions

View File

@@ -40,13 +40,18 @@ return new class extends Migration
});
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);
DB::table('stories')
->join('stories_authors', 'stories_authors.id', '=', 'stories.author_id')
->whereNull('stories.creator_id')
->whereNotNull('stories_authors.user_id')
->select(['stories.id as story_id', 'stories_authors.user_id as creator_id'])
->orderBy('stories.id')
->get()
->each(function ($row): void {
DB::table('stories')
->where('id', (int) $row->story_id)
->update(['creator_id' => (int) $row->creator_id]);
});
}
}

View File

@@ -0,0 +1,26 @@
<?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
{
Schema::table('users', function (Blueprint $table): void {
$table->unsignedInteger('xp')->default(0)->after('allow_messages_from');
$table->unsignedInteger('level')->default(1)->after('xp');
$table->string('rank', 50)->default('Newbie')->after('level');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
$table->dropColumn(['xp', 'level', 'rank']);
});
}
};

View File

@@ -0,0 +1,31 @@
<?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
{
Schema::create('user_xp_logs', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->string('action', 100);
$table->unsignedInteger('xp');
$table->unsignedBigInteger('reference_id')->nullable();
$table->timestamp('created_at')->nullable();
$table->index(['user_id', 'created_at']);
$table->index(['user_id', 'action']);
$table->index(['user_id', 'reference_id']);
});
}
public function down(): void
{
Schema::dropIfExists('user_xp_logs');
}
};

View File

@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
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
{
Schema::create('achievements', function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('description');
$table->string('icon', 80);
$table->unsignedInteger('xp_reward')->default(0);
$table->string('type', 40);
$table->string('condition_type', 40);
$table->unsignedInteger('condition_value');
$table->timestamps();
$table->index(['type', 'condition_type']);
});
DB::table('achievements')->insert([
[
'name' => 'First Upload',
'slug' => 'first-upload',
'description' => 'Publish your first artwork.',
'icon' => 'fa-image',
'xp_reward' => 100,
'type' => 'Uploads',
'condition_type' => 'upload_count',
'condition_value' => 1,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '10 Uploads',
'slug' => 'ten-uploads',
'description' => 'Publish 10 artworks.',
'icon' => 'fa-layer-group',
'xp_reward' => 250,
'type' => 'Uploads',
'condition_type' => 'upload_count',
'condition_value' => 10,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '100 Uploads',
'slug' => 'hundred-uploads',
'description' => 'Publish 100 artworks.',
'icon' => 'fa-fire',
'xp_reward' => 1200,
'type' => 'Uploads',
'condition_type' => 'upload_count',
'condition_value' => 100,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'First Like Received',
'slug' => 'first-like-received',
'description' => 'Receive your first artwork like.',
'icon' => 'fa-heart',
'xp_reward' => 75,
'type' => 'Engagement',
'condition_type' => 'likes_received',
'condition_value' => 1,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '100 Likes',
'slug' => 'hundred-likes-received',
'description' => 'Receive 100 artwork likes.',
'icon' => 'fa-heart-circle-check',
'xp_reward' => 300,
'type' => 'Engagement',
'condition_type' => 'likes_received',
'condition_value' => 100,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '1000 Likes',
'slug' => 'thousand-likes-received',
'description' => 'Receive 1000 artwork likes.',
'icon' => 'fa-heart-circle-bolt',
'xp_reward' => 1500,
'type' => 'Engagement',
'condition_type' => 'likes_received',
'condition_value' => 1000,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'First Follower',
'slug' => 'first-follower',
'description' => 'Earn your first follower.',
'icon' => 'fa-user-plus',
'xp_reward' => 100,
'type' => 'Social',
'condition_type' => 'followers_count',
'condition_value' => 1,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '100 Followers',
'slug' => 'hundred-followers',
'description' => 'Earn 100 followers.',
'icon' => 'fa-users',
'xp_reward' => 500,
'type' => 'Social',
'condition_type' => 'followers_count',
'condition_value' => 100,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'First Story',
'slug' => 'first-story',
'description' => 'Publish your first story.',
'icon' => 'fa-feather-pointed',
'xp_reward' => 100,
'type' => 'Stories',
'condition_type' => 'stories_published',
'condition_value' => 1,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => '10 Stories',
'slug' => 'ten-stories',
'description' => 'Publish 10 stories.',
'icon' => 'fa-book-open-reader',
'xp_reward' => 350,
'type' => 'Stories',
'condition_type' => 'stories_published',
'condition_value' => 10,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Reached Level 5',
'slug' => 'level-five',
'description' => 'Reach level 5 in the XP system.',
'icon' => 'fa-star',
'xp_reward' => 400,
'type' => 'Milestones',
'condition_type' => 'level_reached',
'condition_value' => 5,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Reached Level 7',
'slug' => 'level-seven',
'description' => 'Reach level 7 in the XP system.',
'icon' => 'fa-crown',
'xp_reward' => 1200,
'type' => 'Milestones',
'condition_type' => 'level_reached',
'condition_value' => 7,
'created_at' => now(),
'updated_at' => now(),
],
]);
}
public function down(): void
{
Schema::dropIfExists('achievements');
}
};

View File

@@ -0,0 +1,28 @@
<?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
{
Schema::create('user_achievements', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('achievement_id')->constrained('achievements')->cascadeOnDelete();
$table->timestamp('unlocked_at');
$table->unique(['user_id', 'achievement_id']);
$table->index(['user_id', 'unlocked_at']);
});
}
public function down(): void
{
Schema::dropIfExists('user_achievements');
}
};

View File

@@ -0,0 +1,30 @@
<?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
{
Schema::create('leaderboards', function (Blueprint $table): void {
$table->id();
$table->string('type', 20);
$table->unsignedBigInteger('entity_id');
$table->decimal('score', 14, 2)->default(0);
$table->string('period', 20);
$table->timestamps();
$table->unique(['type', 'period', 'entity_id']);
$table->index(['type', 'period', 'score']);
});
}
public function down(): void
{
Schema::dropIfExists('leaderboards');
}
};

View File

@@ -0,0 +1,48 @@
<?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('story_comments')) {
Schema::create('story_comments', function (Blueprint $table): void {
$table->id();
$table->foreignId('story_id')->constrained('stories')->cascadeOnDelete();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('parent_id')->nullable()->constrained('story_comments')->nullOnDelete();
$table->text('content')->nullable();
$table->longText('raw_content')->nullable();
$table->longText('rendered_content')->nullable();
$table->boolean('is_approved')->default(true);
$table->timestamps();
$table->softDeletes();
$table->index(['story_id', 'created_at']);
$table->index(['story_id', 'parent_id', 'is_approved']);
$table->index(['user_id', 'created_at']);
});
}
if (! Schema::hasTable('story_bookmarks')) {
Schema::create('story_bookmarks', function (Blueprint $table): void {
$table->id();
$table->foreignId('story_id')->constrained('stories')->cascadeOnDelete();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
$table->unique(['story_id', 'user_id']);
$table->index(['user_id', 'created_at']);
});
}
}
public function down(): void
{
Schema::dropIfExists('story_bookmarks');
Schema::dropIfExists('story_comments');
}
};

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
{
public function up(): void
{
if (! Schema::hasTable('artwork_bookmarks')) {
Schema::create('artwork_bookmarks', function (Blueprint $table): void {
$table->id();
$table->foreignId('artwork_id')->constrained('artworks')->cascadeOnDelete();
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
$table->unique(['artwork_id', 'user_id']);
$table->index(['user_id', 'created_at']);
});
}
}
public function down(): void
{
Schema::dropIfExists('artwork_bookmarks');
}
};

View File

@@ -0,0 +1,90 @@
<?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('countries')) {
$this->createCountriesTable();
return;
}
if (Schema::hasColumn('countries', 'id') && Schema::hasColumn('countries', 'iso2')) {
return;
}
$legacyCountries = DB::table('countries')->get();
Schema::drop('countries');
$this->createCountriesTable();
foreach ($legacyCountries as $country) {
$iso2 = strtoupper(trim((string) ($country->iso ?? '')));
if ($iso2 === '' || ! preg_match('/^[A-Z]{2}$/', $iso2)) {
continue;
}
DB::table('countries')->insert([
'iso' => $iso2,
'iso2' => $iso2,
'name' => $country->name ?? null,
'native' => $country->native ?? null,
'phone' => $country->phone ?? null,
'continent' => $country->continent ?? null,
'capital' => $country->capital ?? null,
'currency' => $country->currency ?? null,
'languages' => $country->languages ?? null,
'name_common' => $country->name ?? $iso2,
'name_official' => $country->native ?? null,
'active' => true,
'sort_order' => 1000,
'is_featured' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
public function down(): void
{
Schema::dropIfExists('countries');
}
private function createCountriesTable(): void
{
Schema::create('countries', function (Blueprint $table): void {
$table->id();
$table->char('iso', 2)->nullable()->unique();
$table->char('iso2', 2)->unique();
$table->char('iso3', 3)->nullable()->index();
$table->string('numeric_code', 3)->nullable();
$table->string('name', 255)->nullable();
$table->string('native', 255)->nullable();
$table->string('phone', 50)->nullable();
$table->char('continent', 2)->nullable();
$table->string('capital', 255)->nullable();
$table->string('currency', 32)->nullable();
$table->string('languages', 255)->nullable();
$table->string('name_common', 255);
$table->string('name_official', 255)->nullable();
$table->string('region', 120)->nullable();
$table->string('subregion', 120)->nullable();
$table->text('flag_svg_url')->nullable();
$table->text('flag_png_url')->nullable();
$table->string('flag_emoji', 16)->nullable();
$table->boolean('active')->default(true);
$table->unsignedInteger('sort_order')->default(1000);
$table->boolean('is_featured')->default(false);
$table->timestamps();
$table->index('active');
$table->index('name_common');
$table->index(['is_featured', 'sort_order']);
});
}
};

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 {
public function up(): void
{
if (Schema::hasColumn('users', 'country_id')) {
return;
}
Schema::table('users', function (Blueprint $table): void {
$table->foreignId('country_id')->nullable()->constrained('countries')->nullOnDelete();
});
}
public function down(): void
{
if (! Schema::hasColumn('users', 'country_id')) {
return;
}
Schema::table('users', function (Blueprint $table): void {
$table->dropConstrainedForeignId('country_id');
});
}
};

View File

@@ -0,0 +1,26 @@
<?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', 'last_received_comment_read_at')) {
$table->timestamp('last_received_comment_read_at')->nullable()->after('allow_messages_from');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
if (Schema::hasColumn('users', 'last_received_comment_read_at')) {
$table->dropColumn('last_received_comment_read_at');
}
});
}
};

View File

@@ -0,0 +1,27 @@
<?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_received_comment_reads', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('artwork_comment_id')->constrained('artwork_comments')->cascadeOnDelete();
$table->timestamp('read_at');
$table->timestamps();
$table->unique(['user_id', 'artwork_comment_id'], 'ucr_user_comment_unique');
$table->index(['user_id', 'read_at'], 'ucr_user_read_at_index');
});
}
public function down(): void
{
Schema::dropIfExists('user_received_comment_reads');
}
};

View File

@@ -0,0 +1,26 @@
<?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', 'last_received_comment_read_at')) {
$table->dropColumn('last_received_comment_read_at');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
if (! Schema::hasColumn('users', 'last_received_comment_read_at')) {
$table->timestamp('last_received_comment_read_at')->nullable()->after('allow_messages_from');
}
});
}
};

View File

@@ -0,0 +1,25 @@
<?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
{
Schema::create('dashboard_preferences', function (Blueprint $table): void {
$table->unsignedBigInteger('user_id')->primary();
$table->json('pinned_spaces')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
});
}
public function down(): void
{
Schema::dropIfExists('dashboard_preferences');
}
};