Skip to content

Commit 77193e7

Browse files
Added tags
1 parent 8003b13 commit 77193e7

File tree

12 files changed

+392
-10
lines changed

12 files changed

+392
-10
lines changed

app/Http/Controllers/PostController.php

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public function index()
1919
->when(request("search"), function($query) {
2020
return $query->whereRaw('LOWER(title) LIKE ?', ['%' . strtolower(request('search')) . '%']);
2121
})
22+
->when(request("tags"), function($query) {
23+
$tags = explode(',', request('tags'));
24+
return $query->whereHas('tags', function($query) use ($tags) {
25+
$query->whereIn('name', array_map('trim', $tags));
26+
});
27+
})
28+
->with('tags') // Eager load tags
2229
->withCount('comments')
2330
->latest()
2431
->paginate(10); // Eager load user and categories
@@ -49,21 +56,39 @@ public function store(Request $request)
4956
'content' => 'required',
5057
'categories' => 'array',
5158
'feature_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
59+
'tags' => 'array',
60+
'tags.*' => 'string|max:255',
5261
]);
62+
\DB::beginTransaction();
63+
try {
64+
$tags = $request->input('tags', []);
65+
$tagIds = [];
66+
foreach ($tags as $tagName) {
67+
$tag = \App\Models\Tag::firstOrCreate(['name' => $tagName]);
68+
$tagIds[] = $tag->id;
69+
}
70+
$post = auth()->user()->posts()->create($validated);
5371

54-
$post = auth()->user()->posts()->create($validated);
55-
if ($request->hasFile('feature_image')) {
56-
$post->addMediaFromRequest('feature_image')->toMediaCollection('feature_images');
57-
}
58-
$post->categories()->sync($request->categories);
59-
$post->load('user', 'categories', 'media')->loadCount('comments');
72+
if ($request->hasFile('feature_image')) {
73+
$post->addMediaFromRequest('feature_image')->toMediaCollection('feature_images');
74+
}
75+
76+
$post->categories()->sync($request->categories);
77+
$post->tags()->sync($tagIds);
78+
$post->load('user', 'categories', 'media', 'tags')->loadCount('comments');
79+
// Sync tags
80+
\DB::commit();
81+
return response()->json(new \App\Http\Resources\PostResource($post), 201);
82+
} catch (\Exception $e) {
83+
\DB::rollBack();
6084

61-
return response()->json(new \App\Http\Resources\PostResource($post), 201);
85+
return response()->json(['error' => 'Post creation failed.'], 500);
86+
}
6287
}
6388

6489
public function show(Post $post)
6590
{
66-
$post->load(['user', 'categories', 'comments'])->loadCount('comments');
91+
$post->load(['user', 'categories', 'comments', 'tags'])->loadCount('comments');
6792
return response()->json(new \App\Http\Resources\PostResource($post));
6893
}
6994

@@ -77,15 +102,25 @@ public function update(Request $request, Post $post)
77102
'content' => 'string',
78103
'categories' => 'array',
79104
'feature_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
105+
'tags' => 'array',
106+
'tags.*' => 'string|max:255',
80107
]);
81108

82109
if ($request->hasFile('feature_image')) {
83110
$post->addMediaFromRequest('feature_image')->toMediaCollection('feature_images');
84111
}
85-
112+
86113
$post->update($validated);
114+
if ($request->has('tags')) {
115+
$tagIds = [];
116+
foreach ($request->input('tags') as $tagName) {
117+
$tag = \App\Models\Tag::firstOrCreate(['name' => $tagName]);
118+
$tagIds[] = $tag->id;
119+
}
120+
$post->tags()->sync($tagIds);
121+
}
87122
$post->categories()->sync($request->categories);
88-
$post->load('user', 'categories', 'media')->loadCount('comments');
123+
$post->load('user', 'categories', 'media', 'tags')->loadCount('comments');
89124
return response()->json(new \App\Http\Resources\PostResource($post));
90125
}
91126

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\Tag;
6+
use App\Http\Controllers\Controller;
7+
use App\Http\Resources\TagResource;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Validator;
10+
11+
class TagController extends Controller
12+
{
13+
public function index()
14+
{
15+
$tags = Tag::latest()->paginate(10);
16+
return TagResource::collection($tags);
17+
}
18+
19+
public function store(Request $request)
20+
{
21+
$validator = Validator::make($request->all(), [
22+
'name' => 'required|unique:tags,name|max:255',
23+
]);
24+
25+
if ($validator->fails()) {
26+
return response()->json([
27+
'message' => 'Validation Error.',
28+
'errors' => $validator->errors()
29+
], 422);
30+
}
31+
32+
$tag = Tag::create($request->only('name'));
33+
return new TagResource($tag);
34+
}
35+
36+
public function show(Tag $tag)
37+
{
38+
return new TagResource($tag);
39+
}
40+
41+
public function update(Request $request, Tag $tag)
42+
{
43+
$validator = Validator::make($request->all(), [
44+
'name' => 'required|unique:tags,name,' . $tag->id . '|max:255',
45+
]);
46+
47+
if ($validator->fails()) {
48+
return response()->json([
49+
'message' => 'Validation Error.',
50+
'errors' => $validator->errors()
51+
], 422);
52+
}
53+
54+
$tag->update($request->only('name'));
55+
return new TagResource($tag);
56+
}
57+
58+
public function destroy(Tag $tag)
59+
{
60+
$tag->delete();
61+
return response()->json(['message' => 'Tag deleted successfully.'], 200);
62+
}
63+
}

app/Http/Resources/PostResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public function toArray(Request $request): array
1919
'content' => $this->content,
2020
'created_at' => $this->created_at->toDateTimeString(),
2121
'updated_at' => $this->updated_at->toDateTimeString(),
22+
'tags' => $this->whenLoaded('tags') ? $this->whenLoaded('tags')->pluck('name') : [],
2223
'author' => [
2324
'id' => $this->user->id,
2425
'name' => $this->user->name,

app/Http/Resources/TagResource.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Resources\Json\JsonResource;
7+
8+
class TagResource extends JsonResource
9+
{
10+
/**
11+
* Transform the resource into an array.
12+
*/
13+
public function toArray(Request $request): array
14+
{
15+
return [
16+
'id' => $this->id,
17+
'name' => $this->name,
18+
];
19+
}
20+
}

app/Models/Post.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ public function getRouteKeyName()
4545
{
4646
return 'slug';
4747
}
48+
49+
public function tags()
50+
{
51+
return $this->belongsToMany(Tag::class);
52+
}
4853
}

app/Models/Tag.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Factories\HasFactory;
6+
use Illuminate\Database\Eloquent\Model;
7+
8+
class Tag extends Model
9+
{
10+
use HasFactory;
11+
protected $fillable = ['name'];
12+
13+
public function posts()
14+
{
15+
return $this->belongsToMany(Post::class);
16+
}
17+
}

database/factories/TagFactory.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Database\Factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
7+
/**
8+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
9+
*/
10+
class TagFactory extends Factory
11+
{
12+
/**
13+
* Define the model's default state.
14+
*
15+
* @return array<string, mixed>
16+
*/
17+
public function definition(): array
18+
{
19+
return [
20+
'name' => $this->faker->unique()->word(),
21+
];
22+
}
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('tags', function (Blueprint $table) {
15+
$table->id();
16+
$table->string('name')->unique();
17+
$table->timestamps();
18+
$table->softDeletes();
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*/
25+
public function down(): void
26+
{
27+
Schema::dropIfExists('tags');
28+
}
29+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('post_tag', function (Blueprint $table) {
15+
$table->id();
16+
$table->foreignId('post_id')->constrained()->onDelete('cascade');
17+
$table->foreignId('tag_id')->constrained()->onDelete('cascade');
18+
$table->timestamps();
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*/
25+
public function down(): void
26+
{
27+
Schema::dropIfExists('post_tag');
28+
}
29+
};

routes/api.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Http\Controllers\UserController;
1010
use App\Http\Controllers\RolePermissionController;
1111
use App\Http\Controllers\MediaController;
12+
use App\Http\Controllers\TagController;
1213

1314

1415
Route::post('register', [AuthController::class, 'register']);
@@ -33,6 +34,8 @@
3334
Route::apiResource('posts', PostController::class);
3435
// Category Routes
3536
Route::apiResource('categories', CategoryController::class);
37+
// Tag Routes
38+
Route::apiResource('tags', TagController::class);
3639

3740
// Comment Routes
3841
Route::get('posts/{postId}/comments', [CommentController::class, 'index']);

0 commit comments

Comments
 (0)