Building a Blog in Laravel – Part4 – List, Edit and Delete Posts

Now we extend the main functionality of the site that we started in the previous post. We can create a user and a post, now we add the ability to List, Edit and Delete posts. We will be creating two list views. One for the main page that lists the latest 5, the other being under the users profile listing all their posts.

<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\User;
use App\Posts;
use Illuminate\Foundation\Testing\DatabaseMigrations;


class PostTestWithoutMiddleware extends TestCase
{
    use DatabaseMigrations;

    /**
     * Check user can login and see new-post form
     */
    public function testNewPostGet()
    {
        $user = $this->authenticateUser();

        $this->actingAs($user)
            ->withSession(['foo' => 'bar'])
            ->get('new-post')
            ->assertSuccessful();
    }

    /**
     * Check invalid login/nologin cannot open /new-post
     */
    public function testCreateNewPostInvalidUser()
    {
        $user = factory(User::class)->create();

        $this->actingAs($user)
            ->withSession(['users' => 'fred bloggs'])
            ->get('/new-post')
            ->assertStatus(302);
    }

    /**
     * Check valid user can open /new-post
     */
    public function testNewPostCreation()
    {
        $this->withoutMiddleware();

        $user = $this->authenticateUser();

        $case = factory(Posts::class)->raw(
            [
                'title' => 'test',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $response = $this->actingAs($user)
            ->post('/new-post', $case);

        $response->assertStatus(302);
        $response->assertSee('edit/test');
    }

    /**
     * Check user can update post
     */
    public function testNewPostCreationUpdate()
    {
        $this->withoutMiddleware();

        $user = $this->authenticateUser();

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'title' => 'test',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $response = $this->actingAs($user)
            ->post('/new-post', $case);

        $response->assertSee('edit/test');
        $response = $this->actingAs($user)->post('/update', $case);
        $response->assertStatus(302);
        $response->assertSessionHas('message', "Post updated successfully");

        $response = $this->actingAs($user)->get('/test', $case);
        $response->assertSuccessful();
    }

    /**
     * Create a post then delete it
     */
    public function testUserCanDeletePost()
    {
        $this->withoutMiddleware();

        $user = $this->authenticateUser();

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'id', 1,
                'title' => 'test',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $response = $this->actingAs($user)
            ->post('/new-post', $case);

        $response->assertSee('/test');
        $response = $this->actingAs($user)->get('delete/1');
        $response->assertStatus(302);
        $response->assertSessionHas('message', "Post deleted Successfully");

    }

    /**
     * Test listing posts
     */
    public function testCanListPosts()
    {
        $this->withoutMiddleware();

        $user = $this->authenticateUser();

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'id', 1,
                'title' => 'test',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $this->actingAs($user)
            ->post('/new-post', $case);

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'id', 2,
                'title' => 'test2',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $this->actingAs($user)
            ->post('/new-post', $case);


        $response = $this->get('/');
        $response->assertStatus(200);
        $response->assertSee('test');
        $response->assertSee('test2');
    }

    /**
     * Test listing posts
     */
    public function testUserCanListPosts()
    {
        $this->withoutMiddleware();
        $user = $this->authenticateUser();

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'id', 1,
                'title' => 'test',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $this->actingAs($user)
            ->post('/new-post', $case);

        $case = factory(Posts::class)->raw(
            [
                'author_id', 1,
                'id', 2,
                'title' => 'test2',
                'body' => '123',
                'slug' => 'adkf'
            ]);

        $this->actingAs($user)
            ->post('/new-post', $case);

        $response = $this->actingAs($user)->get('user/1/posts');
        $response->assertSuccessful();
        $response->assertSee('test');
        $response->assertSee('test2');
    }

    public function testAnyoneCanViewPost()
    {
        factory(Posts::class)->create(
            [
                'author_id' => 1,
                'id' => 1,
                'title' => 'test',
                'body' => '123',
                'slug' => 'test',
                'active' => True,

            ]);

        $response = $this->get('/test');
        $response->assertSee('/test');
    }

    /**
     * Create mock user ensuring role is set to author
     *
     * @return User
     */
    public function authenticateUser(): User
    {
        return factory(User::class)->create([
            'id' => 1,
            'name' => 'fred',
            'role' => 'author']);
    }
}

Firstly we need to add some more routes. At this point I also added a route for Login since it wasn’t working. I also moved the controller show into the HomeController. This way it does not require authentication to view a post. The most challenging part of doing that was getting the test working. See the above test called “testAnyoneCanViewPost”.

Route::get('edit/{slug}','PostController@edit');

Route::post('update','PostController@update');

Route::get('delete/{id}','PostController@destroy');

Route::get('/{slug}',['as' => 'post', 'uses' => 'HomeController@show'])->where('slug', '[A-Za-z0-9-_]+');

Route::get('/user/{id}/posts','PostController@list');

Route::get('/auth/logout', 'Auth\LoginController@logout');

Now that’s done we need to add the controllers. These are as follows (note: I have added an authentication helper in the constructor method which will redirect to the login page if not already logged in):

PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Posts;

class PostController extends Controller
{
    public function __construct()
    {
        // If user isn't logged in this will redirect to the login page.
        $this->middleware('auth');
    }

    /**
     * Display create post form
     *
     * @param Request $request
     * @return $this|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function create(Request $request)
    {
        // if user can post i.e. user is admin or author
        if($request->user()->can_post())
        {
            return view('posts.create');
        }
        else
        {
            return redirect('/')->withErrors('You have not sufficient permissions for writing post');
        }
    }

    /**
     * Write the post to database.  Takes input from create post form above.
     *
     * @param Request $request
     * @return mixed
     */
    public function store(Request $request)
    {
        $post = new Posts();
        $post->title = $request->get('title');
        $post->body = $request->get('body');
        $post->slug = str_slug($post->title);
        $post->author_id = $request->user()->id;
        if($request->has('save'))
        {
            $post->active = 0;
            $message = 'Post saved successfully';
        }
        else
        {
            $post->active = 1;
            $message = 'Post published successfully';
        }
        $post->save();
        return redirect('edit/'.$post->slug)->withMessage($message);
    }

    /**
     * Display existing post in edit form.
     *
     * @param Request $request
     * @param $slug
     * @return $this
     */
    public function edit(Request $request,$slug)
    {
        $post = Posts::where('slug',$slug)->first();
        if($post && ($request->user()->id == $post->author_id || $request->user()->is_admin()))
            return view('posts.edit')->with('post',$post);
        return redirect('/')->withErrors('you have not sufficient permissions');
    }

    /**
     * Write changes from derived from edit form to database
     *
     * @param Request $request
     * @return $this
     */
    public function update(Request $request)
    {
        $post_id = $request->input('post_id');
        $post = Posts::find($post_id);
        if($post && ($post->author_id == $request->user()->id || $request->user()->is_admin()))
        {
            $title = $request->input('title');
            $slug = str_slug($title);
            $duplicate = Posts::where('slug',$slug)->first();
            if($duplicate)
            {
                if($duplicate->id != $post_id)
                {
                    return redirect('edit/'.$post->slug)->withErrors('Title already exists.')->withInput();
                }
                else
                {
                    $post->slug = $slug;
                }
            }
            $post->title = $title;
            $post->body = $request->input('body');
            if($request->has('save'))
            {
                $post->active = 0;
                $message = 'Post saved successfully';
                $landing = 'edit/'.$post->slug;
            }
            else {
                $post->active = 1;
                $message = 'Post updated successfully';
                $landing = $post->slug;
            }
            $post->save();
            return redirect($landing)->withMessage($message);
        }
        else
        {
            return redirect('/')->withErrors('you have not sufficient permissions');
        }
    }

    /**
     * @param $slug
     * @return $this
     */
    public function show($slug)
    {
        $post = Posts::where('slug',$slug)->first();
        if(!$post)
        {
            return redirect('/')->withErrors('requested page not found');
        }
        $comments = $post->comments;
        return view('posts.show')->withPost($post)->withComments($comments);
    }

    /**
     * List all posts from logged in user
     *
     * @param $id
     * @return mixed
     */
    public function list($id)
    {
        $posts = Posts::where('author_id',$id)->orderBy('created_at','desc')->paginate(5);
        $title = 'Latest Posts';
        return view('list')->withPosts($posts)->withTitle($title);
        return view('list')->withTitle('list');
    }

    /**
     * @param Request $request
     * @param $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy(Request $request, $id)
    {
        $post = Posts::find($id);
        if($post && ($post->author_id == $request->user()->id || $request->user()->is_admin()))
        {
            $post->delete();
            $data['message'] = 'Post deleted Successfully';
        }
        else
        {
            $data['errors'] = 'Invalid Operation. You have not sufficient permissions';
        }
        return redirect('/')->with($data);
    }
}

HomeController.php

<?php

namespace App\Http\Controllers;

use App\Posts;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function index()
    {
        //fetch 5 posts from database which are active and latest
        $posts = Posts::where('active',1)->orderBy('created_at','desc')->paginate(5);
        //page heading
        $title = 'Latest Posts';
        //return home.blade.php template from resources/views folder
        return view('home')->withPosts($posts)->withTitle($title);
        return view('home')->withTitle('home');
    }

    /**
     * @param $slug
     * @return $this
     */
    public function show($slug)
    {
        $post = Posts::where('slug',$slug)->first();
        if(!$post)
        {
            return redirect('/')->withErrors('requested page not found');
        }
        $comments = $post->comments;
        return view('posts.show')->withPost($post)->withComments($comments);
    }

}

At this point all your tests should pass. The only part left to do is the User Profile improve the appearance of the site.

Copyright © 2020 | Ben Hutton