STIKES Baktara Gorontalo +62-8535-1133-341 contact@stikes-baktara.ac.id




← Back to Posts

My First Post

Published: 29 Dec 2025, 11:13

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

// Intervention Image v3 (Laravel integration)
use Intervention\Image\Laravel\Facades\Image;

/**
* File: app/Http/Controllers/PostController.php
* Purpose: Blog Post CRUD controller.
*
* What this controller does:
* - Public:
* - index(): list posts
* - show(): show post detail
*
* - Auth required (routes protected):
* - create(): show create form
* - store(): save new post + optional image upload + thumbnail
* - edit(): show edit form (owner only)
* - update(): update post (owner only) + optional image replacement
* - destroy(): delete post (owner only) + delete stored images
*
* Important beginner note:
* Authorization ("owner only") is implemented in this controller using abort(403).
* Later we can improve this using Policies/Gates, but this is the simplest clear approach.
*/
class PostController extends Controller
{
/**
* Owner-only guard.
* If the logged-in user is not the post owner, stop with 403 Forbidden.
*/
private function abortIfNotOwner(Post $post): void
{
if ($post->user_id !== auth()->id()) {
abort(403, 'You are not allowed to modify this post.');
}
}

/**
* Show list of posts.
* URL: GET /posts
*/
public function index()
{
$posts = Post::latest()->paginate(9);

return view('posts.index', compact('posts'));
}

/**
* Show a single post by slug.
* URL: GET /posts/{slug}
*/
public function show(string $slug)
{
$post = Post::where('slug', $slug)->firstOrFail();

return view('posts.show', compact('post'));
}

/**
* Show the create post form.
* URL: GET /posts/create
* Middleware: auth (in routes)
*/
public function create()
{
return view('posts.create');
}

/**
* Store a new post with optional image upload + thumbnail generation.
* URL: POST /posts
* Middleware: auth (in routes)
*/
public function store(Request $request)
{
/*
|--------------------------------------------------------------------------
| 1) Validate request data
|--------------------------------------------------------------------------
*/
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
'image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'], // 4MB
]);

/*
|--------------------------------------------------------------------------
| 2) Generate unique slug
|--------------------------------------------------------------------------
*/
$baseSlug = Str::slug($validated['title']);
$slug = $baseSlug;
$counter = 2;

while (Post::where('slug', $slug)->exists()) {
$slug = $baseSlug . '-' . $counter;
$counter++;
}

/*
|--------------------------------------------------------------------------
| 3) Default image paths (null if no upload)
|--------------------------------------------------------------------------
*/
$imagePath = null;
$thumbPath = null;

/*
|--------------------------------------------------------------------------
| 4) Handle image upload (optional)
|--------------------------------------------------------------------------
*/
if ($request->hasFile('image')) {
$file = $request->file('image');

// One base filename for original + thumbnail
$filenameBase = Str::uuid()->toString();
$imageFilename = $filenameBase . '.' . $file->getClientOriginalExtension();
$thumbFilename = $filenameBase . '.webp';

/*
| Store original image
| Path: storage/app/public/posts/
| URL : /storage/posts/filename.ext
*/
$imagePath = $file->storeAs('posts', $imageFilename, 'public');

/*
| Prepare thumbnail path
| Path: storage/app/public/posts/thumbs/
| URL : /storage/posts/thumbs/filename.webp
*/
$thumbPath = 'posts/thumbs/' . $thumbFilename;
$thumbFullPath = storage_path('app/public/' . $thumbPath);

// Ensure thumbnail directory exists
if (!is_dir(dirname($thumbFullPath))) {
mkdir(dirname($thumbFullPath), 0755, true);
}

/*
| Generate thumbnail (Intervention Image v3)
| cover() = crop to exact size
| toWebp(85) = encode WebP quality 85
*/
Image::read($file->getPathname())
->cover(480, 320)
->toWebp(85)
->save($thumbFullPath);
}

/*
|--------------------------------------------------------------------------
| 5) Save post to database
|--------------------------------------------------------------------------
*/
Post::create([
'title' => $validated['title'],
'slug' => $slug,
'content' => $validated['content'],
'image_path' => $imagePath,
'thumb_path' => $thumbPath,
'user_id' => auth()->id(), // ✅ important for dashboard filtering + ownership
]);

/*
|--------------------------------------------------------------------------
| 6) Redirect back to list with success message
|--------------------------------------------------------------------------
*/
return redirect()
->route('posts.index')
->with('success', 'Post created successfully.');
}

/**
* Show edit form.
* URL: GET /posts/{slug}/edit
* Middleware: auth (in routes)
*/
public function edit(string $slug)
{
$post = Post::where('slug', $slug)->firstOrFail();

// ✅ owner-only
$this->abortIfNotOwner($post);

return view('posts.edit', compact('post'));
}

/**
* Update a post (and optionally replace image).
* URL: PUT /posts/{slug}
* Middleware: auth (in routes)
*/
public function update(Request $request, string $slug)
{
$post = Post::where('slug', $slug)->firstOrFail();

// ✅ owner-only
$this->abortIfNotOwner($post);

/*
|--------------------------------------------------------------------------
| 1) Validate
|--------------------------------------------------------------------------
*/
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
'image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
]);

/*
|--------------------------------------------------------------------------
| 2) Update slug if title changed (keep it unique)
|--------------------------------------------------------------------------
*/
$newBaseSlug = Str::slug($validated['title']);
$newSlug = $newBaseSlug;

if ($newSlug !== $post->slug) {
$counter = 2;

while (
Post::where('slug', $newSlug)
->where('id', '!=', $post->id)
->exists()
) {
$newSlug = $newBaseSlug . '-' . $counter;
$counter++;
}
} else {
$newSlug = $post->slug; // unchanged
}

/*
|--------------------------------------------------------------------------
| 3) If new image uploaded: delete old files, store new ones
|--------------------------------------------------------------------------
*/
if ($request->hasFile('image')) {

// Delete old files if exist
if ($post->image_path) {
Storage::disk('public')->delete($post->image_path);
}
if ($post->thumb_path) {
Storage::disk('public')->delete($post->thumb_path);
}

$file = $request->file('image');

// New filenames
$filenameBase = Str::uuid()->toString();
$imageFilename = $filenameBase . '.' . $file->getClientOriginalExtension();
$thumbFilename = $filenameBase . '.webp';

// Store original image
$imagePath = $file->storeAs('posts', $imageFilename, 'public');

// Create thumbnail
$thumbPath = 'posts/thumbs/' . $thumbFilename;
$thumbFullPath = storage_path('app/public/' . $thumbPath);

if (!is_dir(dirname($thumbFullPath))) {
mkdir(dirname($thumbFullPath), 0755, true);
}

Image::read($file->getPathname())
->cover(480, 320)
->toWebp(85)
->save($thumbFullPath);

// Save new paths to the post
$post->image_path = $imagePath;
$post->thumb_path = $thumbPath;
}

/*
|--------------------------------------------------------------------------
| 4) Save updated fields
|--------------------------------------------------------------------------
*/
$post->title = $validated['title'];
$post->slug = $newSlug;
$post->content = $validated['content'];
$post->save();

return redirect()
->route('posts.show', $post->slug)
->with('success', 'Post updated successfully.');
}

/**
* Delete a post and its images (original + thumbnail).
* URL: DELETE /posts/{slug}
* Middleware: auth (in routes)
*/
public function destroy(string $slug)
{
$post = Post::where('slug', $slug)->firstOrFail();

// ✅ owner-only
$this->abortIfNotOwner($post);

// Delete original image if exists
if ($post->image_path) {
Storage::disk('public')->delete($post->image_path);
}

// Delete thumbnail if exists
if ($post->thumb_path) {
Storage::disk('public')->delete($post->thumb_path);
}

// Delete DB record
$post->delete();

return redirect()
->route('posts.index')
->with('success', 'Post deleted successfully.');
}
}

Contact

STIKES Baktara Gorontalo

+62-8535-1133-341

contact@stikes-baktara.ac.id

Gallery

Newsletter

Subscribe to our newsletter.