Work on tidying up, blog page, pagination, and more

todo: in models for photos and posts, add belongsTo and hasMany respectively
main
Niisse 8 months ago
parent d9131d7520
commit 3dae0c93fb

@ -2,63 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Models\Post;
class BlogController extends Controller class BlogController extends Controller
{ {
/**
* Display a listing of the resource.
*/
public function index() public function index()
{ {
// return view('blog.index', [
} 'blogPosts' => Post::where('type', 'blog')->paginate(5)
]);
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
} }
} }

@ -2,7 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\BlogPost; use App\Models\Post;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -26,7 +26,7 @@ class HomeController extends Controller
public function index() public function index()
{ {
$tags = Tag::limit(5)->get(); $tags = Tag::limit(5)->get();
$posts = BlogPost::all()->reverse(); $posts = Post::all()->reverse();
return view('home', [ return view('home', [
'posts' => $posts, 'posts' => $posts,

@ -3,21 +3,20 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest; use App\Http\Requests\StorePostRequest;
use App\Models\BlogPost; use App\Models\Post;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class PostController extends Controller class PostController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth')->except('show'); // $this->middleware('auth')->except('show');
} }
public function index() public function index()
{ {
//todo: change this to overview of blog posts for blog post. Add admin method for mgmt
return view('posts.index', [ return view('posts.index', [
'posts' => BlogPost::all()->reverse() 'posts' => Post::all()->reverse()
]); ]);
} }
@ -28,21 +27,26 @@ class PostController extends Controller
public function store(StorePostRequest $request) public function store(StorePostRequest $request)
{ {
$validated = $request->validated(); Post::create([
'title' => $request->input('title'),
BlogPost::create([ 'type' => $request->input('type'),
'title' => $validated['title'], 'content' => $request->input('content'),
'content' => $validated['content'], 'location' => $request->input('location'),
'location' => $validated['location'],
]); ]);
return redirect()->route('posts.index'); if ($request->hasFile('uploads')) {
$this->processUploadedImages($request);
}
// todo: register images
return redirect()->route('posts.index')->with('success');
} }
public function show($id) public function show($id)
{ {
return view('posts.show', [ return view('posts.show', [
'post' => BlogPost::find($id) 'post' => Post::find($id)
]); ]);
} }
@ -57,4 +61,22 @@ class PostController extends Controller
public function destroy($id) public function destroy($id)
{ {
} }
public function processUploadedImages(StorePostRequest $request): void
{
$prefix = strtolower(str_replace(' ', '_', $request->input('title')));
$imageFileTypes = ['jpg', 'jpeg', 'png', 'webp'];
foreach ($request->allFiles()['uploads'] as $file) {
if (in_array($file->getClientOriginalExtension(), $imageFileTypes)) {
$imageName = $prefix . $file->getClientOriginalName() . '_' . $file->getClientOriginalExtension();
$file->move(public_path('uploads'), $imageName);
}
}
}
public function processUploadedMusic(StorePostRequest $request): void
{
}
} }

@ -10,9 +10,10 @@ class StorePostRequest extends FormRequest
{ {
return [ return [
'title' => ['required', 'string', 'max:255'], 'title' => ['required', 'string', 'max:255'],
'type' => ['required'],
'content' => ['required'], 'content' => ['required'],
'location' => ['nullable'], 'location' => ['nullable'],
'files' => ['nullable', 'mimes:jpg,jpeg,png', 'max:20000'], 'uploads' => ['nullable', 'max:20000'],
'tags' => ['nullable'], 'tags' => ['nullable'],
]; ];
} }

@ -7,12 +7,13 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
class BlogPost extends Model class Post extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = [
'title', 'title',
'type',
'content', 'content',
'location', 'location',
]; ];

@ -16,7 +16,7 @@ class Tag extends Model
public function blogPosts(): BelongsToMany public function blogPosts(): BelongsToMany
{ {
return $this->belongsToMany(BlogPost::class); return $this->belongsToMany(Post::class);
} }
} }

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -19,6 +20,6 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// Paginator::useBootstrapFive();
} }
} }

@ -2,20 +2,21 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\BlogPost; use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class BlogPostFactory extends Factory class PostFactory extends Factory
{ {
protected $model = BlogPost::class; protected $model = Post::class;
public function definition(): array public function definition(): array
{ {
return [ return [
'title' => $this->faker->word(), 'title' => $this->faker->words(),
'content' => $this->faker->word(), 'content' => $this->faker->word(),
'location' => $this->faker->word(), 'location' => $this->faker->word(),
'type' => 'blog',
'created_at' => Carbon::now(), 'created_at' => Carbon::now(),
'updated_at' => Carbon::now(), 'updated_at' => Carbon::now(),

@ -11,10 +11,11 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('blog_posts', function (Blueprint $table) { Schema::create('posts', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('title'); $table->string('title');
$table->string('type')->default('blog');
$table->longText('content'); $table->longText('content');
$table->string('location')->nullable(); $table->string('location')->nullable();
@ -27,6 +28,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('blog_posts'); Schema::dropIfExists('posts');
} }
}; };

@ -1,6 +1,6 @@
<?php <?php
use App\Models\BlogPost; use App\Models\Post;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
@ -13,10 +13,10 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('blog_post_tag', function (Blueprint $table) { Schema::create('post_tag', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignIdFor(BlogPost::class)->constrained(); $table->foreignIdFor(Post::class)->constrained();
$table->foreignIdFor(Tag::class)->constrained(); $table->foreignIdFor(Tag::class)->constrained();
$table->timestamps(); $table->timestamps();
@ -28,6 +28,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('blog_post_tag'); Schema::dropIfExists('post_tag');
} }
}; };

@ -3,7 +3,7 @@
namespace Database\Seeders; namespace Database\Seeders;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents; // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use App\Models\BlogPost; use App\Models\Post;
use App\Models\Photo; use App\Models\Photo;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -18,7 +18,7 @@ class DatabaseSeeder extends Seeder
$this->call([ $this->call([
UserSeeder::class, UserSeeder::class,
TagSeeder::class, TagSeeder::class,
BlogPostSeeder::class, PostSeeder::class,
]); ]);
// todo: remove when finished testing // todo: remove when finished testing

@ -2,37 +2,37 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\BlogPost; use App\Models\Post;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class BlogPostSeeder extends Seeder class PostSeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
*/ */
public function run(): void public function run(): void
{ {
$article = "**Lorem Ipsum** is simply dummy text of the __printing__ and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. \r\n **Lorem Ipsum** is simply dummy text of the __printing__ and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."; $article = "**Lorem Ipsum** is simply dummy text of the __printing__ and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. \r\n **Lorem Ipsum** is simply dummy text of the __printing__ and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\n";
for ($i = 0; $i < 30; $i++) { for ($i = 0; $i < 30; $i++) {
BlogPost::factory([ Post::factory([
'title' => 'Test Blog Post ' . $i, 'title' => 'Test Blog Post ' . $i,
'content' => $article, 'content' => $article . $article . $article,
'location' => null, 'location' => null,
'created_at' => Carbon::today()->subDays(rand(1, 100)) 'created_at' => Carbon::today()->subDays(rand(1, 100))
])->create()->tags()->saveMany(Tag::inRandomOrder()->limit(random_int(1, 3))->get()); ])->create()->tags()->saveMany(Tag::inRandomOrder()->limit(random_int(1, 3))->get());
} }
BlogPost::factory([ Post::factory([
'title' => 'Test Blog Post 1', 'title' => 'Test Blog Post 1',
'content' => $article, 'content' => $article,
'location' => null, 'location' => null,
'created_at' => Carbon::today()->subDays(rand(1, 100)) 'created_at' => Carbon::today()->subDays(rand(1, 100))
])->create()->tags()->saveMany(Tag::inRandomOrder()->limit(random_int(1, 3))->get()); ])->create()->tags()->saveMany(Tag::inRandomOrder()->limit(random_int(1, 3))->get());
BlogPost::factory([ Post::factory([
'title' => 'Blog Post Test 2', 'title' => 'Blog Post Test 2',
'content' => $article, 'content' => $article,
'location' => null, 'location' => null,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

@ -0,0 +1,66 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
@foreach($blogPosts as $post)
<div class="col-md-9 px-4">
<h2 class="mt-0!">{{$post->title}}</h2>
<div class="mt-3 pr-2" style="text-align: justify">
<x-markdown>
{{$post->content}}
</x-markdown>
</div>
<div class="text-secondary mt-3">
<div class="d-inline-flex flex-row gap-3">
<div>
<i class="bi bi-calendar-week"></i> {{$post->created_at->format('Y-m-d')}}
</div>
@if(!is_null($post->location))
<div>
<i class="bi bi-geo-alt-fill"></i>
{{ $post->location }}
</div>
@endif
<div>
<i class="bi bi-tags-fill"></i>
@if(sizeof($post->tags) > 0)
@foreach($post->tags as $tag)
<a class="text-secondary" href="/">{{$tag->name}}</a>,
@endforeach
@else
<a class="text-secondary" href="/">No tags</a>
@endif
</div>
</div>
</div>
<div class="pt-3 pb-1">
<hr>
</div>
</div>
<div class="col-md-3">
<div class="mt-5">
<i class="bi bi-clock"></i> Posted on {{ $post->created_at->format('Y-m-d') }} <br>
<i class="bi bi-pencil"></i> Last updated on {{ $post->updated_at->format('Y-m-d') }} <br>
todo: location <br>
todo: tags <br>
</div>
</div>
@endforeach
<div class="col-md-9">
{{ $blogPosts->links() }}
</div>
</div>
</div>
@endsection

@ -14,9 +14,9 @@
<h2 class="mt-0!">{{$post->title}}</h2> <h2 class="mt-0!">{{$post->title}}</h2>
<div class="mt-3 pr-2" style="text-align: justify"> <div class="mt-3 pr-2" style="text-align: justify">
<x-markdown> <x-markdown>
{{array_slice(explode("\r\n", $post->content), 0, 1)[0]}} {{array_slice(explode("\r\n", $post->content), 0, 1)[0]}}
</x-markdown> </x-markdown>
</div> </div>
<div> <div>
@ -26,11 +26,39 @@
<div class="text-secondary mt-3"> <div class="text-secondary mt-3">
<div class="d-inline-flex flex-row gap-3"> <div class="d-inline-flex flex-row gap-3">
<div> <div>
<i class="bi bi-journal-text"></i> Blog Entry @switch($post->type)
@case('blog')
<i class="bi bi-journal-text"></i>
Blog Post
@break
@case('music')
<i class="bi bi-cassette"></i>
Music
@break
@case('gallery')
<i class="bi bi-camera"></i>
Photo Gallery
@break
@default
<i class="bi bi-bug"></i>
Unknown
@endswitch
</div> </div>
<div> <div>
<i class="bi bi-calendar-week"></i> {{$post->created_at->format('Y-m-d')}} <i class="bi bi-calendar-week"></i> {{$post->created_at->format('Y-m-d')}}
</div> </div>
@if(!is_null($post->location))
<div>
<i class="bi bi-geo-alt-fill"></i>
{{ $post->location }}
</div>
@endif
<div> <div>
<i class="bi bi-tags-fill"></i> <i class="bi bi-tags-fill"></i>

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html lang="{{ str_replace('_', '-', app()->getLocale())}}" data-bs-theme="system">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -22,7 +22,7 @@
<main class="py-4"> <main class="py-4">
@yield('content') @yield('content')
@include('layouts.footer') @include('layouts.footer')

@ -2,31 +2,47 @@
<div class="d-flex flex-column gap-3 px-2 pb-1"> <div class="d-flex flex-column gap-3 px-2 pb-1">
<div> <div>
<h5>Recent Posts</h5> <h5>
<i class="bi bi-clock"></i>
Recent Posts
</h5>
<ol> <ol>
<li>Something</li> @foreach($posts->take(3) as $post)
<li>Other thing</li> <a href="{{route('posts.show', $post)}}" class="text-dark">
<li>A photo, perhaps</li> <li> {{$post->title}} </li>
</a>
@endforeach
</ol> </ol>
</div> </div>
<div> <div>
<h5>Categories</h5> <h5>
<i class="bi bi-tags"></i>
Categories
</h5>
<div class="d-flex flex-column px-2 mb-3"> <div class="d-flex flex-column px-2 mb-3">
@foreach($tags as $tag) <ul>
<a> @foreach($tags as $tag)
<i class="bi bi-tags-fill"></i> <li>
{{$tag->name}} <a>
</a> {{-- <i class="bi bi-tags-fill"></i>--}}
@endforeach {{$tag->name}}
</a>
</li>
@endforeach
</ul>
<a class="text-dark" href="/"> <a class="text-dark" href="/">
<i class="bi bi-link-45deg"></i> {{-- <i class="bi bi-link-45deg"></i>--}}
More...</a> More...</a>
</div> </div>
</div> </div>
<div> <div>
<h5>My other spaces</h5> <h5>
<i class="bi bi-link-45deg"></i>
My other spaces
</h5>
<div class="d-flex flex-column px-2"> <div class="d-flex flex-column px-2">
<a class="link-dark" href="https://niisse.dev"> <a class="link-dark" href="https://niisse.dev">

@ -45,7 +45,7 @@
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="" class="nav-link @if(request()->routeIs('posts.*')) active @endif">Blog</a> <a href="{{route('blog')}}" class="nav-link @if(request()->routeIs('blog')) active @endif">Blog</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">

@ -5,7 +5,7 @@
@section('content') @section('content')
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-9 px-4"> <div class="col-md-7 px-4">
<h3>Create new post</h3> <h3>Create new post</h3>
<br> <br>
@ -14,31 +14,87 @@
@csrf @csrf
<div class="form-group"> <div class="form-group">
<label for="type">Post Type</label>
<select name="type" id="type" class="form-select @error('type') is-invalid @enderror">
<option value="blog" selected>Blog Post</option>
<option value="music">Music</option>
<option value="gallery">Photo Gallery</option>
</select>
@error('type')
<span class="invalid-feedback" role="alert">
{{ $message }}
</span>
@enderror
</div>
<div class="form-group mt-4">
<label for="title">Post Title</label> <label for="title">Post Title</label>
<input type="text" class="form-control" id="title" name="title" required/> <input type="text" class="form-control @error('title') is-invalid @enderror" id="title"
name="title" required/>
@error('title')
<span class="invalid-feedback" role="alert">
{{ $message }}
</span>
@enderror
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<label for="content">Post Content</label> <label for="content">Post Content</label>
<div class="form-floating"> <div class="form-floating">
<textarea class="form-control" placeholder="Enter markdown here" id="content" required <textarea class="form-control @error('content') is-invalid @enderror" required
placeholder="Enter markdown here" id="content"
name="content" style="height: 200px"></textarea> name="content" style="height: 200px"></textarea>
</div> </div>
@error('content')
<span class="invalid-feedback" role="alert">
{{ $message }}
</span>
@enderror
</div> </div>
<div class="mt-4"> <div class="form-group mt-4">
<label for="files" class="form-label">Upload pictures</label> <label for="images" class="form-label">Upload files</label>
<input class="form-control" type="file" id="files" name="files[]" multiple> <div class="input-group">
<input class="form-control @error('uploads') is-invalid @enderror" type="file" id="uploads"
name="uploads[]" multiple>
<div class="btn btn-outline-danger" onclick="clearUploads()"
title="Clear selected files">
<i class="bi bi-x-circle"></i>
</div>
</div>
@error('uploads')
<span class="invalid-feedback" role="alert">
{{$message}}
</span>
@enderror
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<label for="location">Location</label> <label for="location">Location</label>
<input type="text" name="location" id="location" class="form-control"> <input type="text" name="location" id="location"
class="form-control @error('location') is-invalid @enderror">
@error('location')
<span class="invalid-feedback" role="alert">
{{$message}}}
</span>
@enderror
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<label for="tags">Tags</label> <label for="tags">Tags</label>
<input type="text" name="tags" id="tags" class="form-control"> <input type="text" name="tags" id="tags"
class="form-control @error('tags') is-invalid @enderror">
@error('tags')
<span class="invalid-feedback" role="alert">
{{$message}}
</span>
@enderror
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
@ -50,4 +106,10 @@
</div> </div>
</div> </div>
</div> </div>
<script>
function clearUploads() {
document.getElementById('uploads').value = null;
}
</script>
@endsection @endsection

@ -28,6 +28,7 @@ Route::get('/blog', [BlogController::class, 'index'])->name('blog');
Route::resource('/posts', PostController::class); Route::resource('/posts', PostController::class);
Route::middleware('auth')->group(function() { Route::middleware('auth')->group(function() {
Route::get('/admin', [AdminController::class, 'index'])->name('admin'); Route::get('/admin', [AdminController::class, 'index'])->name('admin');
}); });
Loading…
Cancel
Save