<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class extends Migration
{
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->foreignId('parent_id')->nullable()->constrained('categories')->nullOnDelete();
$table->timestamps();
$table->index('parent_id');
});
}
public function down(): void
{
Schema::dropIfExists('categories');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class extends Migration
{
public function up(): void
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tags');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('excerpt')->nullable();
$table->longText('content');
$table->string('image')->nullable();
$table->boolean('is_featured')->default(false);
$table->enum('status', ['draft', 'pending', 'published'])->default('draft');
$table->timestamp('published_at')->nullable();
// Statistics
$table->unsignedInteger('views_count')->default(0);
$table->unsignedInteger('comments_count')->default(0);
$table->unsignedInteger('likes_count')->default(0);
// SEO Fields
$table->string('meta_title')->nullable();
$table->text('meta_description')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index('user_id');
$table->index('category_id');
$table->index(['status', 'published_at']);
$table->index('published_at');
});
// Fulltext search (MySQL 8+ InnoDB OK)
// Schema::table('posts', function (Blueprint $table) {
// $table->fullText(['title', 'content']);
// });
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')->constrained('posts')->cascadeOnDelete();
$table->foreignId('tag_id')->constrained('tags')->cascadeOnDelete();
$table->timestamps();
$table->unique(['post_id', 'tag_id']);
$table->index(['tag_id', 'post_id']);
});
}
public function down(): void
{
Schema::dropIfExists('post_tag');
Schema::dropIfExists('posts');
}
};
Comment table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class extends Migration
{
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->morphs('commentable');
$table->foreignId('parent_id')->nullable()->constrained('comments')->cascadeOnDelete();
$table->text('body');
$table->enum('status', ['pending', 'approved', 'spam'])->default('pending');
$table->timestamps();
$table->softDeletes();
$table->index(['commentable_type', 'commentable_id', 'created_at']);
$table->index('parent_id');
$table->index(['status', 'created_at']);
$table->index('user_id');
});
}
public function down(): void
{
Schema::dropIfExists('comments');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class extends Migration
{
public function up(): void
{
Schema::create('likes', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->morphs('likeable');
$table->timestamps();
$table->unique(['user_id', 'likeable_type', 'likeable_id']);
$table->index('user_id');
});
}
public function down(): void
{
Schema::dropIfExists('likes');
}
};
protected $fillable = [
'name', 'slug', 'parent_id',
];
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
protected $fillable = [
'name',
'slug',
];
public function posts(): BelongsToMany
{
return $this->belongsToMany(Post::class, 'post_tag')->withTimestamps();
}
protected $fillable = [
'user_id',
'category_id',
'title',
'slug',
'excerpt',
'content',
'status',
'published_at',
];
protected $casts = [
'published_at' => 'datetime',
];
public function author(): BelongsTo
{
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class, 'post_tag')->withTimestamps();
}
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
public function likes(): MorphMany
{
return $this->morphMany(Like::class, 'likeable');
}
Comment
protected $fillable = [
'user_id',
'commentable_type',
'commentable_id',
'parent_id',
'body',
'status',
];
public function commentable(): MorphTo
{
return $this->morphTo();
}
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function replies(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
protected $fillable = [
'user_id',
'likeable_type',
'likeable_id',
];
public function likeable(): MorphTo
{
return $this->morphTo();
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}