blog api created

This commit is contained in:
parsa aghaei 2024-10-13 20:16:08 +03:30
parent 4f101885a8
commit d98bc7bbb0
27 changed files with 1022 additions and 44 deletions

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Blog;
use App\Http\Controllers\Controller;
use App\Http\Resources\CategoryResource;
use App\Http\Resources\PostResource;
use App\Models\Blog\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class CategoryController extends Controller
{
// نمایش لیست دسته‌بندی‌ها با صفحه‌بندی
public function index(Request $request)
{
$categories = Category::with('posts')->paginate(10);
return CategoryResource::collection($categories);
}
// نمایش یک دسته‌بندی خاص
public function show($id)
{
$category = Category::with('posts')->findOrFail($id);
return new CategoryResource($category);
}
/**
* نمایش دسته‌بندی و پست‌های مرتبط با آن بر اساس slug
*
* @param \Illuminate\Http\Request $request
* @param string $slug
* @return \Illuminate\Http\JsonResponse
*/
public function getCategoryWithPosts(Request $request, $slug)
{
// یافتن دسته‌بندی بر اساس slug
$category = Category::where('slug', $slug)->first();
if (!$category) {
return response()->json([
'message' => 'دسته‌بندی مورد نظر یافت نشد.'
], 404);
}
// دریافت تعداد پست‌ها در هر صفحه (پیش‌فرض: ۱۰)
$perPage = $request->input('per_page', 10);
// دریافت پست‌های مربوط به این دسته‌بندی به ترتیب جدیدترین‌ها
$posts = $category->posts()->with(['category', 'tags', 'author'])
->orderBy('published_at', 'desc')
->paginate($perPage);
// بازگشت اطلاعات دسته‌بندی و پست‌ها
return response()->json([
'category' => new CategoryResource($category),
'posts' => PostResource::collection($posts),
'pagination' => [
'total' => $posts->total(),
'per_page' => $posts->perPage(),
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
'from' => $posts->firstItem(),
'to' => $posts->lastItem(),
],
], 200);
}
// ذخیره یک دسته‌بندی جدید
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:categories,name',
'slug' => 'required|string|unique:categories,slug',
'description' => 'required|string',
'short_description' => 'nullable|string|max:500',
'image' => 'nullable|url',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$category = Category::create($validator->validated());
return new CategoryResource($category);
}
// به‌روزرسانی یک دسته‌بندی خاص
public function update(Request $request, $id)
{
$category = Category::findOrFail($id);
$validator = Validator::make($request->all(), [
'name' => 'sometimes|required|string|max:255|unique:categories,name,' . $category->id,
'slug' => 'sometimes|required|string|unique:categories,slug,' . $category->id,
'description' => 'sometimes|required|string',
'short_description' => 'nullable|string|max:500',
'image' => 'nullable|url',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$category->update($validator->validated());
return new CategoryResource($category);
}
// حذف یک دسته‌بندی خاص
public function destroy($id)
{
$category = Category::findOrFail($id);
// قبل از حذف، مطمئن شوید که پست‌ها به دسته‌بندی دیگری منتقل شده‌اند یا حذف شوند
// برای سادگی، اینجا فقط دسته‌بندی را حذف می‌کنیم
$category->delete();
return response()->json(['message' => 'Category deleted successfully'], 200);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Blog;
use App\Http\Controllers\Controller;
use App\Http\Resources\CategoryResource;
use App\Http\Resources\PostResource;
use App\Models\Blog\Category;
use App\Models\Blog\Post;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* برگرداندن داده‌های مورد نیاز برای صفحه‌ی اصلی
*
* @return \Illuminate\Http\JsonResponse
*/
public function index()
{
// 1. 5 پست آخر
$latestPosts = Post::with(['category', 'tags', 'author'])
->orderBy('published_at', 'desc')
->take(5)
->get();
// 2. 5 دسته‌بندی آخر
$latestCategories = Category::orderBy('created_at', 'desc')
->take(5)
->get();
// 3. 5 پست با بیشترین بازدید
$mostViewedPosts = Post::with(['category', 'tags', 'author'])
->orderBy('views', 'desc')
->take(5)
->get();
// 4. 4 پست تصادفی
$randomPosts = Post::with(['category', 'tags', 'author'])
->inRandomOrder()
->take(4)
->get();
// بازگشت به صورت JSON با استفاده از منابع (Resources)
return response()->json([
'latest_posts' => PostResource::collection($latestPosts),
'latest_categories' => CategoryResource::collection($latestCategories),
'most_viewed_posts' => PostResource::collection($mostViewedPosts),
'random_posts' => PostResource::collection($randomPosts),
], 200);
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers\Blog;
use App\Http\Controllers\Controller;
use App\Http\Resources\PostResource;
use App\Models\Blog\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PostController extends Controller
{
// نمایش لیست پست‌ها با فیلتر و صفحه‌بندی
public function index(Request $request)
{
// دریافت پارامترهای فیلتر از درخواست
$filters = $request->only(['category', 'tag', 'search', 'author', 'published']);
// دریافت پارامترهای مرتب‌سازی از درخواست
$sortField = $request->get('sort_field', 'published_at'); // فیلد پیش‌فرض: published_at
$sortDirection = $request->get('sort_direction', 'desc'); // جهت پیش‌فرض: نزولی
// دریافت پارامترهای صفحه‌بندی از درخواست
$perPage = $request->get('per_page', 10); // تعداد آیتم‌ها در هر صفحه (پیش‌فرض: ۱۰)
// ساخت Query با اعمال فیلترها و مرتب‌سازی
$query = Post::with(['category', 'tags', 'author'])
->filter($filters)
->sort($sortField, $sortDirection);
// اعمال صفحه‌بندی
$posts = $query->paginate($perPage);
// بازگشت نتایج به صورت JSON با استفاده از Resource
return PostResource::collection($posts);
}
// نمایش یک پست خاص
public function show($slug)
{
// یافتن پست بر اساس slug و بارگذاری روابط مرتبط
$post = Post::with(['category', 'tags', 'author', 'relatedPosts.category', 'relatedPosts.tags', 'relatedPosts.author'])
->where('slug', $slug)
->firstOrFail(); // در صورت عدم وجود پست، خطای 404 برمی‌گرداند
// بازگشت پست و پست‌های مرتبط به صورت JSON با استفاده از منابع
return response()->json([
'post' => new PostResource($post),
'related_posts' => PostResource::collection($post->relatedPosts->sortByDesc('published_at')->take(5)), // بازگشت ۵ پست مرتبط جدیدترین
], 200);
}
// ذخیره یک پست جدید
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'body' => 'required|string',
'excerpt' => 'nullable|string|max:500',
'slug' => 'required|string|unique:posts,slug',
'author_id' => 'required|exists:users,id',
'category_id' => 'required|exists:categories,id',
'featured_image' => 'nullable|url',
'views' => 'nullable|integer',
'likes' => 'nullable|integer',
'published_at' => 'nullable|date',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$post = Post::create($validator->validated());
if ($request->has('tags')) {
$post->tags()->attach($request->tags);
}
return new PostResource($post->load(['category', 'tags', 'author']));
}
// به‌روزرسانی یک پست خاص
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
$validator = Validator::make($request->all(), [
'title' => 'sometimes|required|string|max:255',
'body' => 'sometimes|required|string',
'excerpt' => 'nullable|string|max:500',
'slug' => 'sometimes|required|string|unique:posts,slug,' . $post->id,
'author_id' => 'sometimes|required|exists:users,id',
'category_id' => 'sometimes|required|exists:categories,id',
'featured_image' => 'nullable|url',
'views' => 'nullable|integer',
'likes' => 'nullable|integer',
'published_at' => 'nullable|date',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$post->update($validator->validated());
if ($request->has('tags')) {
$post->tags()->sync($request->tags);
}
return new PostResource($post->load(['category', 'tags', 'author']));
}
// حذف یک پست خاص
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->tags()->detach();
$post->relatedPosts()->detach();
$post->delete();
return response()->json(['message' => 'Post deleted successfully'], 200);
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Blog;
use App\Http\Controllers\Controller;
use App\Http\Resources\TagResource;
use App\Models\Blog\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class TagController extends Controller
{
// نمایش لیست تگ‌ها با صفحه‌بندی
public function index(Request $request)
{
$tags = Tag::with('posts')->paginate(10);
return TagResource::collection($tags);
}
// نمایش یک تگ خاص
public function show($id)
{
$tag = Tag::with('posts')->findOrFail($id);
return new TagResource($tag);
}
// ذخیره یک تگ جدید
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:tags,name',
'slug' => 'required|string|unique:tags,slug',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$tag = Tag::create($validator->validated());
return new TagResource($tag);
}
// به‌روزرسانی یک تگ خاص
public function update(Request $request, $id)
{
$tag = Tag::findOrFail($id);
$validator = Validator::make($request->all(), [
'name' => 'sometimes|required|string|max:255|unique:tags,name,' . $tag->id,
'slug' => 'sometimes|required|string|unique:tags,slug,' . $tag->id,
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$tag->update($validator->validated());
return new TagResource($tag);
}
// حذف یک تگ خاص
public function destroy($id)
{
$tag = Tag::findOrFail($id);
$tag->posts()->detach();
$tag->delete();
return response()->json(['message' => 'Tag deleted successfully'], 200);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'short_description' => $this->short_description,
'image' => $this->image,
'posts_count' => $this->whenLoaded('posts', function () {
return $this->posts->count();
}),
'created_at' => Carbon::parse($this->created_at)->format('M d Y'),
'updated_at' => Carbon::parse($this->updated_at)->format('M d Y'),
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Carbon\Carbon;
class PostResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'body' => $this->body,
'author' => new UserResource($this->author),
'category' => new CategoryResource($this->category),
'tags' => TagResource::collection($this->tags),
'featured_image' => $this->featured_image,
'views' => $this->views,
'likes' => $this->likes,
'published_at' => $this->published_at,
'created_at' => Carbon::parse($this->created_at)->format('M d Y'),
'updated_at' => Carbon::parse($this->updated_at)->format('M d Y'),
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class TagResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'posts_count' => $this->whenLoaded('posts', function () {
return $this->posts->count();
}),
'created_at' => Carbon::parse($this->created_at)->format('M d Y'),
'updated_at' => Carbon::parse($this->updated_at)->format('M d Y'),
];
}
}

View File

@ -14,6 +14,12 @@ class User extends ResourceCollection
*/ */
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
return parent::toArray($request); return [
'id' => $this->id,
'name' => $this->name,
// اضافه کردن سایر فیلدهای مورد نیاز
'email' => $this->email,
'created_at' => $this->created_at,
];
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Http\Resources; namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@ -18,8 +19,8 @@ public function toArray(Request $request): array
'id' => $this->id, 'id' => $this->id,
'name' => $this->name, 'name' => $this->name,
'email' => $this->email, 'email' => $this->email,
'created_at' => $this->created_at, 'created_at' => Carbon::parse($this->created_at)->format('M d Y'),
'updated_at' => $this->updated_at, 'updated_at' => Carbon::parse($this->updated_at)->format('M d Y'),
]; ];
} }
} }

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models\Blog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'short_description',
'image',
];
// رابطه با پست‌ها
public function posts()
{
return $this->hasMany(Post::class);
}
}

99
app/Models/Blog/Post.php Normal file
View File

@ -0,0 +1,99 @@
<?php
namespace App\Models\Blog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'body',
'excerpt',
'slug',
'author_id',
'category_id',
'featured_image',
'views',
'likes',
'published_at',
];
// رابطه با نویسنده
public function author()
{
return $this->belongsTo(User::class, 'author_id');
}
// رابطه با دسته‌بندی
public function category()
{
return $this->belongsTo(Category::class);
}
// رابطه با تگ‌ها
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
// پست‌های مرتبط
public function relatedPosts()
{
return $this->belongsToMany(Post::class, 'related_posts', 'post_id', 'related_post_id')->withTimestamps();
}
// اسکوپ فیلتر
public function scopeFilter($query, $filters)
{
if (isset($filters['category'])) {
$query->whereHas('category', function ($q) use ($filters) {
$q->where('slug', $filters['category']);
});
}
if (isset($filters['tag'])) {
$query->whereHas('tags', function ($q) use ($filters) {
$q->where('slug', $filters['tag']);
});
}
if (isset($filters['search'])) {
$query->where(function ($q) use ($filters) {
$q->where('title', 'like', '%' . $filters['search'] . '%')
->orWhere('body', 'like', '%' . $filters['search'] . '%');
});
}
if (isset($filters['author'])) {
$query->whereHas('author', function ($q) use ($filters) {
$q->where('id', $filters['author']);
});
}
if (isset($filters['published'])) {
$query->whereNotNull('published_at');
}
return $query;
}
/**
* Scope برای اعمال مرتب‌سازی
*/
public function scopeSort($query, $sortField, $sortDirection = 'asc')
{
// بررسی اینکه فیلد مرتب‌سازی معتبر است یا خیر
$allowedSortFields = ['published_at', 'views', 'likes', 'title'];
if (in_array($sortField, $allowedSortFields)) {
$sortDirection = strtolower($sortDirection) === 'desc' ? 'desc' : 'asc';
$query->orderBy($sortField, $sortDirection);
}
return $query;
}
}

22
app/Models/Blog/Tag.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models\Blog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
];
// رابطه با پست‌ها
public function posts()
{
return $this->belongsToMany(Post::class)->withTimestamps();
}
}

View File

@ -3,6 +3,8 @@
namespace App\Models; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Models\Blog\Post;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -44,4 +46,9 @@ protected function casts(): array
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
public function posts()
{
return $this->hasMany(Post::class, 'author_id');
}
} }

View File

@ -0,0 +1,29 @@
<?php
namespace Database\Factories\Blog;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category>
*/
class CategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$name = $this->faker->unique()->word;
return [
'name' => ucfirst($name),
'slug' => Str::slug($name),
'description' => $this->faker->paragraph,
'short_description' => $this->faker->sentence,
'image' => $this->faker->imageUrl(640, 480, 'business', true),
];
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Database\Factories\Blog;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Blog\Post;
use App\Models\Blog\Category;
use App\Models\User;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
protected $model = Post::class;
public function definition(): array
{
$title = $this->faker->sentence;
return [
'title' => $title,
'body' => $this->faker->paragraphs(5, true),
'excerpt' => $this->faker->text(200),
'slug' => Str::slug($title) . '-' . $this->faker->unique()->numberBetween(1, 1000),
'author_id' => User::inRandomOrder()->first()->id ?? User::factory(),
'category_id' => Category::inRandomOrder()->first()->id ?? Category::factory(),
'featured_image' => $this->faker->imageUrl(800, 600, 'technics', true),
'views' => $this->faker->numberBetween(0, 1000),
'likes' => $this->faker->numberBetween(0, 500),
'published_at' => $this->faker->dateTimeBetween('-1 years', 'now'),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Database\Factories\Blog;
use App\Models\Blog\Tag;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
*/
class TagFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
protected $model = Tag::class;
public function definition(): array
{
$name = $this->faker->unique()->word;
return [
'name' => ucfirst($name),
'slug' => Str::slug($name),
];
}
}

View File

@ -37,7 +37,7 @@ public function definition(): array
*/ */
public function unverified(): static public function unverified(): static
{ {
return $this->state(fn (array $attributes) => [ return $this->state(fn(array $attributes) => [
'email_verified_at' => null, 'email_verified_at' => null,
]); ]);
} }

View File

@ -0,0 +1,32 @@
<?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::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('slug')->unique();
$table->text('description');
$table->text('short_description')->nullable();
$table->string('image')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};

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
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('slug')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tags');
}
};

View File

@ -0,0 +1,39 @@
<?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::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->text('excerpt'); // توضیحات مختصر
$table->string('slug')->unique();
$table->unsignedBigInteger('author_id'); // نویسنده
$table->foreign('author_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('category_id'); // دسته بندی
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->string('featured_image')->nullable(); // تصویر شاخص
$table->integer('views')->default(0); // تعداد بازدید
$table->integer('likes')->default(0); // تعداد لایک
$table->timestamp('published_at')->nullable(); // تاریخ انتشار
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};

View File

@ -0,0 +1,36 @@
<?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::create('post_tag', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('post_id');
$table->unsignedBigInteger('tag_id');
$table->timestamps();
// ایجاد کلیدهای خارجی
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
// جلوگیری از ورود تکراری
$table->unique(['post_id', 'tag_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('post_tag');
}
};

View File

@ -0,0 +1,36 @@
<?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::create('related_posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('post_id');
$table->unsignedBigInteger('related_post_id');
$table->timestamps();
// ایجاد کلیدهای خارجی
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->foreign('related_post_id')->references('id')->on('posts')->onDelete('cascade');
// جلوگیری از ورود تکراری
$table->unique(['post_id', 'related_post_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('related_posts');
}
};

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\AboutMe; use App\Models\AboutMe;
use App\Models\Skill; use App\Models\Skill;
use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Console\Seeds\WithoutModelEvents;
@ -15,44 +16,53 @@ public function run()
{ {
// ایجاد رکورد AboutMe // ایجاد رکورد AboutMe
$aboutMe = AboutMe::create([ $aboutMe = AboutMe::create([
'title' => 'About Me Title', 'title' => 'About Me',
'description' => 'This is a brief description about me.', 'description' => 'I am a web developer with over 10 years of experience in creating dynamic and user-centric web applications. Specializing in React and Next.js, I build scalable solutions that enhance performance and user experience. I enjoy tackling complex challenges, from improving legacy systems to developing new applications. My focus on frontend development and seamless backend integration allows me to deliver innovative results while staying current with industry trends.',
'image' => '/images/profile.png', 'image' => '/images/profile.png',
]); ]);
// ایجاد رکوردهای Skill // ایجاد رکوردهای Skill
Skill::create([ Skill::create([
'title' => 'FrontEnd', 'title' => 'HTML & CSS (SASS, Responsive Design)',
'image' => '/images/frontend.png', 'image' => '/images/frontend.png',
'description' => 'UI design description.', 'description' => 'HTML & CSS (SASS, Responsive Design) description',
'link' => 'http://example.com/frontend', 'link' => 'http://example.com/frontend',
'percentage' => 95,
'about_me_id' => $aboutMe->id,
]);
Skill::create([
'title' => 'React.js and Next.js Development',
'image' => '/images/backend.png',
'description' => 'React.js and Next.js Development description.',
'link' => 'http://example.com/backend',
'percentage' => 90,
'about_me_id' => $aboutMe->id,
]);
Skill::create([
'title' => 'Backend Development (RESTful APIs, Laravel)',
'image' => '/images/ui.png',
'description' => 'Backend Development (RESTful APIs, Laravel) description.',
'link' => 'http://example.com/ui',
'percentage' => 85,
'about_me_id' => $aboutMe->id,
]);
Skill::create([
'title' => 'WordPress Development and Customization',
'image' => '/images/ux.png',
'description' => 'WordPress Development and Customization description.',
'link' => 'http://example.com/ux',
'percentage' => 90, 'percentage' => 90,
'about_me_id' => $aboutMe->id, 'about_me_id' => $aboutMe->id,
]); ]);
Skill::create([ Skill::create([
'title' => 'BackEnd', 'title' => 'Version Control (Git, GitHub)',
'image' => '/images/backend.png',
'description' => 'BackEnd description.',
'link' => 'http://example.com/backend',
'percentage' => 80,
'about_me_id' => $aboutMe->id,
]);
Skill::create([
'title' => 'UI Design',
'image' => '/images/ui.png',
'description' => 'UI design description.',
'link' => 'http://example.com/ui',
'percentage' => 70,
'about_me_id' => $aboutMe->id,
]);
Skill::create([
'title' => 'UX Design',
'image' => '/images/ux.png', 'image' => '/images/ux.png',
'description' => 'UX design description.', 'description' => 'Version Control (Git, GitHub) description.',
'link' => 'http://example.com/ux', 'link' => 'http://example.com/ux',
'percentage' => 83, 'percentage' => 80,
'about_me_id' => $aboutMe->id, 'about_me_id' => $aboutMe->id,
]); ]);
} }

View File

@ -0,0 +1,46 @@
<?php
namespace Database\Seeders\Blog;
use App\Models\Blog\Category;
use App\Models\Blog\Post;
use App\Models\Blog\Tag;
use App\Models\User;
use Illuminate\Database\Seeder;
class BlogSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// انتخاب کاربر با id = 1
$user = User::find(1);
if (!$user) {
// اگر کاربر با id 1 وجود نداشت، خطا ایجاد کن یا پیام مناسبی بده
$this->command->error('User with id 1 not found.');
return;
}
// ایجاد دسته‌بندی‌ها
Category::factory(10)->create();
// ایجاد تگ‌ها
Tag::factory(20)->create();
// ایجاد پست‌ها و تخصیص به کاربر با id = 1
Post::factory(50)->create([
'author_id' => $user->id, // اختصاص پست‌ها به کاربر id 1
])->each(function ($post) {
// اختصاص تگ‌ها به پست
$tags = Tag::inRandomOrder()->take(rand(2, 5))->pluck('id');
$post->tags()->attach($tags);
// ایجاد پست‌های مرتبط
$relatedPosts = Post::inRandomOrder()->where('id', '!=', $post->id)->take(rand(1, 3))->pluck('id');
$post->relatedPosts()->attach($relatedPosts);
});
}
}

View File

@ -3,6 +3,7 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\User; use App\Models\User;
use Database\Seeders\Blog\BlogSeeder;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents; // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -16,11 +17,14 @@ public function run(): void
// User::factory(10)->create(); // User::factory(10)->create();
User::factory()->create([ User::factory()->create([
'name' => 'Test User', 'name' => 'parsa aghayi',
'email' => 'test@example.com', 'email' => 'ceo@parsaaghayi.ir',
]); ]);
$this->call([ $this->call([
AboutMeSeeder::class,
ProjectSeeder::class, ProjectSeeder::class,
ServiceSeeder::class,
BlogSeeder::class
// دیگر seeders // دیگر seeders
]); ]);
} }

View File

@ -15,28 +15,28 @@ public function run()
{ {
$services = [ $services = [
[ [
'title' => 'UI/UX', 'title' => 'Custom Web Development',
'description' => 'Lorem ipsum dolor sit amet consectetur. Morbi diam nisi nam diam interdum', 'description' => 'We create tailored web applications that meet your unique business requirements, utilizing the latest technologies for optimal performance.',
'imageSrc' => '/images/ui-ux-vector.svg', 'imageSrc' => '/images/ui-ux-vector.svg',
'altText' => 'UI/UX Vector', 'altText' => 'Custom Web Development Vector',
], ],
[ [
'title' => 'Web Design', 'title' => 'Frontend Development',
'description' => 'Lorem ipsum dolor sit amet consectetur. Morbi diam nisi nam diam interdum', 'description' => 'Our frontend development services focus on crafting engaging, responsive user interfaces that enhance user experience across all devices.',
'imageSrc' => '/images/web-design-vector.svg', 'imageSrc' => '/images/web-design-vector.svg',
'altText' => 'Web Design Vector', 'altText' => 'Frontend Development Vector',
], ],
[ [
'title' => 'App Design', 'title' => 'Backend Development',
'description' => 'Lorem ipsum dolor sit amet consectetur. Morbi diam nisi nam diam interdum', 'description' => 'We provide reliable backend development solutions, building robust server-side architectures and APIs to ensure smooth and secure application functionality.',
'imageSrc' => '/images/app-design-vector.svg', 'imageSrc' => '/images/app-design-vector.svg',
'altText' => 'App Design Vector', 'altText' => 'Backend Development Vector',
], ],
[ [
'title' => 'Graphic Design', 'title' => 'Website Maintenance and Support',
'description' => 'Lorem ipsum dolor sit amet consectetur. Morbi diam nisi nam diam interdum', 'description' => 'Our ongoing maintenance and support services keep your website updated, secure, and performing at its best, allowing you to focus on your core business.',
'imageSrc' => '/images/graphic-design-vector.svg', 'imageSrc' => '/images/graphic-design-vector.svg',
'altText' => 'Graphic Design Vector', 'altText' => 'Website Maintenance and Support Vector',
], ],
]; ];

View File

@ -4,6 +4,10 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProjectController; use App\Http\Controllers\ProjectController;
use App\Http\Controllers\AboutMeController; use App\Http\Controllers\AboutMeController;
use App\Http\Controllers\Blog\CategoryController;
use App\Http\Controllers\Blog\HomeController;
use App\Http\Controllers\Blog\PostController;
use App\Http\Controllers\Blog\TagController;
use App\Http\Controllers\SkillController; use App\Http\Controllers\SkillController;
use App\Http\Controllers\ServiceController; use App\Http\Controllers\ServiceController;
@ -16,3 +20,17 @@
Route::resource('about-me', AboutMeController::class); Route::resource('about-me', AboutMeController::class);
Route::resource('skills', SkillController::class); Route::resource('skills', SkillController::class);
Route::apiResource('services', ServiceController::class); Route::apiResource('services', ServiceController::class);
// مسیرهای بلاگ
Route::prefix('blog')->group(function () {
// پست‌ها
Route::apiResource('posts', PostController::class);
// دسته‌بندی‌ها
Route::apiResource('categories', CategoryController::class);
// تگ‌ها
Route::apiResource('tags', TagController::class);
// مسیر Homepage
Route::get('homepage', [HomeController::class, 'index']);
Route::get('categories/{slug}/posts', [CategoryController::class, 'getCategoryWithPosts']);
});