diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 5c0fe80..4cc1fbf 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -49,8 +49,14 @@ class ChapterController extends Controller } else { //chapter number $manga = Manga::where('id', $request->manga_id)->first(); - $latestChap = $manga->chapters()->orderBy('chapter_no', 'desc')->first()->chapter_no; - $formData['chapter_no'] = $latestChap + 1; + + $totalChap = $manga->chapters()->count(); + if($totalChap == 0 ) { + $formData['chapter_no'] = $totalChap + 1; + }else { + $latestChap = $manga->chapters()->orderBy('chapter_no', 'desc')->first()->chapter_no; + $formData['chapter_no'] = $latestChap + 1; + } } Chapter::create($formData); diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php new file mode 100644 index 0000000..cccf191 --- /dev/null +++ b/app/Http/Controllers/CommentController.php @@ -0,0 +1,73 @@ +user()->comments()->create($request->only('comment', 'chapter_id')); + return back()->with(['message' => 'Successfully posted a comment']); + } + + /** + * Display the specified resource. + */ + public function show(Comment $comment) + { + return abort('403'); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Comment $comment) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateCommentRequest $request, Comment $comment) + { + // dd($request); + $this->authorize('update', $comment); + $comment->update(['comment' => $request->comment_edit]); + return back()->with(['message' => 'Comment has been updated!']); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Comment $comment) + { + $this->authorize('delete', $comment); + $comment->delete(); + return back()->with(['message' => 'Comment has been deleted!!']); + } +} diff --git a/app/Http/Controllers/GenreController.php b/app/Http/Controllers/GenreController.php new file mode 100644 index 0000000..b11a546 --- /dev/null +++ b/app/Http/Controllers/GenreController.php @@ -0,0 +1,10 @@ +put('example.txt', 'Contents'); - $mangas = Manga::when(request()->has('search'), function($q) { + $mangas = Manga::with(['chapters']) + ->when(request()->has('search'), function($q) { $keyword = request()->search; $q->where('title', 'like', '%'.$keyword.'%'); }) ->latest('id') - ->paginate(8) - ->withQueryString(); - - return view('index', ['mangas' => $mangas]); + ->paginate(8)->withQueryString(); + $hotMangas = Manga::with(['chapters']) + ->latest('id')->limit('3')->get(); + return view('index', ['mangas' => $mangas, 'hotMangas' => $hotMangas]); } - public function manga($slug) + public function manga(Manga $manga) { - $manga = Manga::where('slug', $slug)->first(); - $chapters = $manga->chapters()->orderBy('chapter_no', 'desc')->paginate(10); - $firstChapter = $manga->chapters()->orderBy('chapter_no', 'asc')->first(); - $lastChapter = $manga->chapters()->latest('chapter_no')->first(); - // dd($firstChapter); + $chapters = $manga->chapters() + ->latest('chapter_no') + ->paginate(10); + $firstChapter = $manga->chapters() + ->orderBy('chapter_no', 'asc')->first(); + $lastChapter = $manga->chapters() + ->latest('chapter_no') + ->first(); + $hotMangas = Manga::with(['chapters']) + ->latest('id')->limit('3')->get(); return view('manga',[ 'manga' => $manga, + 'hotMangas' => $hotMangas, 'chapters' => $chapters, 'firstChapter' => $firstChapter, 'lastChapter' => $lastChapter @@ -40,11 +47,15 @@ class PageController extends Controller public function chapter(Manga $manga,Chapter $chapter) { - $firstChapter = $manga->chapters()->orderBy('chapter_no', 'asc')->first(); - $lastChapter = $manga->chapters()->latest('chapter_no')->first(); + $firstChapter = $manga->chapters() + ->orderBy('chapter_no', 'asc') + ->first(); + $lastChapter = $manga->chapters() + ->latest('chapter_no') + ->first(); return view('chapter_page', [ - 'manga' => $manga, 'chapter' => $chapter, + 'manga' => $manga, 'firstChapter' => $firstChapter, 'lastChapter' => $lastChapter ]); @@ -52,7 +63,6 @@ class PageController extends Controller public function select(Manga $manga, Request $request) { - // dd($request); return redirect()->route('page.chapter', [$manga, $request->chapter_no]); } } diff --git a/app/Http/Controllers/ReplyController.php b/app/Http/Controllers/ReplyController.php new file mode 100644 index 0000000..e3248dc --- /dev/null +++ b/app/Http/Controllers/ReplyController.php @@ -0,0 +1,73 @@ +user()->replies()); + $request->user()->replies()->create($request->only('reply', 'comment_id')); + return back()->with(['message' => 'Successfully made a reply!!']); + } + + /** + * Display the specified resource. + */ + public function show(Reply $reply) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Reply $reply) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateReplyRequest $request, Reply $reply) + { + $this->authorize('update', $reply); + + $reply->update(['reply' => $request->reply_edit]); + return back()->with(['message' => 'Reply has been updated!!']); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Reply $reply) + { + $this->authorize('delete', $reply); + $reply->delete(); + return back()->with(['message' => "Your reply has been deleted"]); + } +} diff --git a/app/Http/Requests/StoreCommentRequest.php b/app/Http/Requests/StoreCommentRequest.php new file mode 100644 index 0000000..a637c38 --- /dev/null +++ b/app/Http/Requests/StoreCommentRequest.php @@ -0,0 +1,29 @@ + + */ + public function rules(): array + { + return [ + 'comment' => 'required|min:1|max:255', + 'chapter_id' => 'required|exists:chapters,id' + ]; + } +} diff --git a/app/Http/Requests/StoreReplyRequest.php b/app/Http/Requests/StoreReplyRequest.php new file mode 100644 index 0000000..d7b65c3 --- /dev/null +++ b/app/Http/Requests/StoreReplyRequest.php @@ -0,0 +1,29 @@ + + */ + public function rules(): array + { + return [ + 'reply' => 'required|min:1|max:255', + 'comment_id' => 'required|exists:comments,id' + ]; + } +} diff --git a/app/Http/Requests/UpdateCommentRequest.php b/app/Http/Requests/UpdateCommentRequest.php new file mode 100644 index 0000000..b7685d9 --- /dev/null +++ b/app/Http/Requests/UpdateCommentRequest.php @@ -0,0 +1,28 @@ + + */ + public function rules(): array + { + return [ + 'comment_edit' => 'required|min:1|max:255', + ]; + } +} diff --git a/app/Http/Requests/UpdateReplyRequest.php b/app/Http/Requests/UpdateReplyRequest.php new file mode 100644 index 0000000..329d183 --- /dev/null +++ b/app/Http/Requests/UpdateReplyRequest.php @@ -0,0 +1,28 @@ + + */ + public function rules(): array + { + return [ + 'reply_edit' => 'required|min:1|max:255' + ]; + } +} diff --git a/app/Models/Chapter.php b/app/Models/Chapter.php index ec4c507..ba95afe 100644 --- a/app/Models/Chapter.php +++ b/app/Models/Chapter.php @@ -18,4 +18,8 @@ class Chapter extends Model { return $this->belongsTo(Manga::class, 'manga_id'); } + + public function comments() { + return $this->hasMany(Comment::class, 'chapter_id'); + } } diff --git a/app/Models/Comment.php b/app/Models/Comment.php new file mode 100644 index 0000000..1469158 --- /dev/null +++ b/app/Models/Comment.php @@ -0,0 +1,23 @@ +belongsTo(User::class); + } + + public function replies() + { + return $this->hasMany(Reply::class, 'comment_id'); + } +} diff --git a/app/Models/Genre.php b/app/Models/Genre.php new file mode 100644 index 0000000..91b1725 --- /dev/null +++ b/app/Models/Genre.php @@ -0,0 +1,11 @@ +belongsTo(User::class); + } + + public function comment() + { + return $this->belongsTo(Comment::class, 'comment_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index a1235b9..b2014aa 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -47,4 +47,14 @@ class User extends Authenticatable { return $this->hasMany(Manga::class, 'author_id'); } + + public function comments() + { + return $this->hasMany(Comment::class); + } + + public function replies() + { + return $this->hasMany(Reply::class); + } } diff --git a/app/Policies/CommentPolicy.php b/app/Policies/CommentPolicy.php new file mode 100644 index 0000000..7b4c7a6 --- /dev/null +++ b/app/Policies/CommentPolicy.php @@ -0,0 +1,76 @@ +role === 'admin') { + // return true; + // } + // } + + /** + * Determine whether the user can view any models. + */ + public function viewAny(User $user): bool + { + // + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Comment $comment): bool + { + // + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + // + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Comment $comment): bool + { + return $comment->user_id === $user->id; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Comment $comment): bool + { + return $comment->user_id === $user->id || $user->role === 'admin'; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Comment $comment): bool + { + // + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Comment $comment): bool + { + // + } +} diff --git a/app/Policies/ReplyPolicy.php b/app/Policies/ReplyPolicy.php new file mode 100644 index 0000000..cca8fa4 --- /dev/null +++ b/app/Policies/ReplyPolicy.php @@ -0,0 +1,66 @@ +id === $reply->user_id; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Reply $reply): bool + { + return $user->id === $reply->user_id || $user->role === 'admin'; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Reply $reply): bool + { + // + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Reply $reply): bool + { + // + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index b44585e..4114673 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -6,9 +6,12 @@ namespace App\Providers; use App\Models\User; use App\Models\Manga; +use App\Models\Reply; use App\Models\Chapter; use App\Policies\MangaPolicy; +use App\Policies\ReplyPolicy; use App\Policies\ChapterPolicy; +use App\Policies\CommentPolicy; use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; @@ -21,7 +24,9 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Manga::class => MangaPolicy::class, - Chapter::class => ChapterPolicy::class + Chapter::class => ChapterPolicy::class, + Comment::class => CommentPolicy::class, + Reply::class => ReplyPolicy::class ]; /** diff --git a/database/factories/CommentFactory.php b/database/factories/CommentFactory.php new file mode 100644 index 0000000..09414e4 --- /dev/null +++ b/database/factories/CommentFactory.php @@ -0,0 +1,23 @@ + + */ +class CommentFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/ReplyFactory.php b/database/factories/ReplyFactory.php new file mode 100644 index 0000000..7686c4e --- /dev/null +++ b/database/factories/ReplyFactory.php @@ -0,0 +1,23 @@ + + */ +class ReplyFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/migrations/2023_09_13_132221_create_comments_table.php b/database/migrations/2023_09_13_132221_create_comments_table.php new file mode 100644 index 0000000..3f29466 --- /dev/null +++ b/database/migrations/2023_09_13_132221_create_comments_table.php @@ -0,0 +1,30 @@ +id(); + $table->longText('comment'); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('chapter_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('comments'); + } +}; diff --git a/database/migrations/2023_09_13_183050_create_replies_table.php b/database/migrations/2023_09_13_183050_create_replies_table.php new file mode 100644 index 0000000..cab3bee --- /dev/null +++ b/database/migrations/2023_09_13_183050_create_replies_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('reply'); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('comment_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('replies'); + } +}; + + diff --git a/database/migrations/2023_09_20_144705_create_genres_table.php b/database/migrations/2023_09_20_144705_create_genres_table.php new file mode 100644 index 0000000..a879da4 --- /dev/null +++ b/database/migrations/2023_09_20_144705_create_genres_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name'); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('genres'); + } +}; diff --git a/database/seeders/CommentSeeder.php b/database/seeders/CommentSeeder.php new file mode 100644 index 0000000..5f45105 --- /dev/null +++ b/database/seeders/CommentSeeder.php @@ -0,0 +1,17 @@ + { + btn.addEventListener('click', function() { + // console.log('click'); + editBox.forEach( box => { + if(btn.id == box.id) { + box.classList.toggle('d-none'); + // console.log(box.classList.contains('d-none')); + if(box.classList.contains('d-none')) { + btn.innerHTML = 'Edit'; + }else { + btn.innerHTML = 'Cancle'; + } + } + }) + }) +}) + + +//reply edit box +let replyEditBtn = document.querySelectorAll('.reply-edit-btn'); +let replyEditBox =document.querySelectorAll('.reply-edit-box'); + +replyEditBtn.forEach( btn => { + btn.addEventListener('click', function() { + // console.log('click'); + replyEditBox.forEach( box => { + if(btn.id == box.id) { + box.classList.toggle('d-none'); + // console.log(box.classList.contains('d-none')); + if(box.classList.contains('d-none')) { + btn.innerHTML = ""; + }else { + btn.innerHTML = ""; + } + } + }) + }) +}) diff --git a/resources/js/reply-box.js b/resources/js/reply-box.js new file mode 100644 index 0000000..ca2ea30 --- /dev/null +++ b/resources/js/reply-box.js @@ -0,0 +1,17 @@ +let replyBtn = document.querySelectorAll(".reply-btn") ; +let replyBox = document.querySelectorAll(".reply-box") ; + +replyBtn.forEach( btn => { + btn.addEventListener('click', function() { + replyBox.forEach(box => { + if(box.id == btn.id) { + box.classList.toggle('d-none'); + if(box.classList.contains('d-none')) { + btn.innerHTML = 'Reply'; + }else { + btn.innerHTML = 'Cancle'; + } + } + }) + }) +}); diff --git a/resources/js/scroll-to-top.js b/resources/js/scroll-to-top.js new file mode 100644 index 0000000..e07ba2c --- /dev/null +++ b/resources/js/scroll-to-top.js @@ -0,0 +1,11 @@ +//scroll to top +const sttBtn = document.querySelector('#sttBtn'); +window.onscroll = () => { + if(document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { + sttBtn.classList.remove('d-none'); + console.log("gt20"); + }else { + sttBtn.classList.add('d-none'); + console.log('lt20'); + } +}; diff --git a/resources/views/chapter_page.blade.php b/resources/views/chapter_page.blade.php index 0f56ef7..b8c736b 100644 --- a/resources/views/chapter_page.blade.php +++ b/resources/views/chapter_page.blade.php @@ -24,6 +24,16 @@ @include('partials.chapter-select-box') @include('partials.chapter-paginate') - +
+ +
+ @include('partials.discussion') +
+ @include('partials.scroll-to-top') + @vite(['resources/js/scroll-to-top.js']) +@endsection + +@section('footer') + @endsection diff --git a/resources/views/components/footer.blade.php b/resources/views/components/footer.blade.php index 210b755..9fe9296 100644 --- a/resources/views/components/footer.blade.php +++ b/resources/views/components/footer.blade.php @@ -1,3 +1,3 @@ -
+

Copyright All Served @MangaDex

diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index 00d60f2..9a72713 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -9,7 +9,7 @@
@endif -
+
@forelse ($mangas as $manga) @@ -24,7 +24,7 @@

-
+
@include('partials.hot-manga')
diff --git a/resources/views/partials/comment-edit-box.blade.php b/resources/views/partials/comment-edit-box.blade.php new file mode 100644 index 0000000..8e0ad95 --- /dev/null +++ b/resources/views/partials/comment-edit-box.blade.php @@ -0,0 +1,14 @@ +
+
+ @csrf + @method('PUT') +
+ + +
+ @error('comment_edit') +

{{$message}}

+ @enderror +
+
diff --git a/resources/views/partials/discussion.blade.php b/resources/views/partials/discussion.blade.php new file mode 100644 index 0000000..1a4cec6 --- /dev/null +++ b/resources/views/partials/discussion.blade.php @@ -0,0 +1,80 @@ +
Comments From Chapter - {{ $chapter->chapter_no }}
+
Discussion
+ +@guest +

You must Register or Login to post a Comment.

+@endguest + +@auth +
+

{{ $chapter->comments->count() }} {{ Str::plural('comment', $chapter->comments->count()) }} +

+

+ {{ auth()->user()->name }} +

+
+
+ @csrf +
+ + + +
+ @error('comment') +

{{ $message }}

+ @enderror +
+@endauth +
+ + +
+ @forelse ($chapter->comments()->latest()->get() as $comment) +
+

+ {{ $comment->user->name }} + {{ $comment->created_at->diffForHumans() }} +

+

{{ $comment->comment }}

+ + @include('partials.comment-edit-box') + + + @include('partials.reply-box') + + +

+ @can('update', $comment) + + + @endcan + @can('delete', $comment) + + + @endcan + + @auth + + @endauth + + + @include('partials.replies') + +

+
+ @csrf + @method('DELETE') +
+ + + +
+ @empty +

There are no comments yet!

+ @endforelse +
diff --git a/resources/views/partials/hot-manga.blade.php b/resources/views/partials/hot-manga.blade.php index 08c945b..7d78f60 100644 --- a/resources/views/partials/hot-manga.blade.php +++ b/resources/views/partials/hot-manga.blade.php @@ -1,28 +1,24 @@
Manga Hot
- @forelse (App\Models\Manga::latest('id')->limit(3)->get() as $hotManga) -
-
-
- - img - -
-
- -

{{ $hotManga->title }}

-
+ @forelse ($hotMangas as $hotManga) +
+
+ + img + +
+
+ +

{{ $hotManga->title }}

+
- @foreach ($hotManga->chapters()->latest('id')->limit(2)->get() as $hotChap) - - @endforeach + @foreach ($hotManga->chapters()->latest('id')->limit(2)->get() as $hotChap) + + @endforeach -
- @empty -

No Hot Manga Yet

+

No Hot Manga Yet

@endforelse
diff --git a/resources/views/partials/replies.blade.php b/resources/views/partials/replies.blade.php new file mode 100644 index 0000000..1ef44f4 --- /dev/null +++ b/resources/views/partials/replies.blade.php @@ -0,0 +1,52 @@ +@if ($comment->replies->count()) + +
+ @foreach ($comment->replies()->latest()->get() as $reply) +
+
+
+ + {{ $reply->user->name }} + + + + + + {{ $comment->user->name }} + + + + {{ $reply->created_at->diffForHumans() }} + +
+ +
+ @can('update', $reply) + + @endcan + + @can('delete', $reply) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+
+

+ {{ $reply->reply }} +

+ + @include('partials.reply-edit-box') +
+ @endforeach +
+@endif diff --git a/resources/views/partials/reply-box.blade.php b/resources/views/partials/reply-box.blade.php new file mode 100644 index 0000000..5e191c8 --- /dev/null +++ b/resources/views/partials/reply-box.blade.php @@ -0,0 +1,18 @@ +@auth +
+

Relying to {{ $comment->user->name }}

+
+ @csrf +
+ + + +
+ @error('reply') +

{{ $message }}

+ @enderror +
+
+@endauth diff --git a/resources/views/partials/reply-edit-box.blade.php b/resources/views/partials/reply-edit-box.blade.php new file mode 100644 index 0000000..86efedf --- /dev/null +++ b/resources/views/partials/reply-edit-box.blade.php @@ -0,0 +1,14 @@ +
+
+ @csrf + @method('PUT') +
+ + +
+ @error('reply_edit') +

{{$message}}

+ @enderror +
+
diff --git a/resources/views/partials/scroll-to-top.blade.php b/resources/views/partials/scroll-to-top.blade.php new file mode 100644 index 0000000..53ccde6 --- /dev/null +++ b/resources/views/partials/scroll-to-top.blade.php @@ -0,0 +1,9 @@ + diff --git a/routes/web.php b/routes/web.php index 4b42301..c73c036 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,13 +1,15 @@ group(function () { Route::get('/', 'index')->name('page.index'); - Route::get('/MangaDex/manga/{slug}', 'manga')->name('page.manga'); - Route::get('/manga/{manga:slug}/chapter/{chapter:chapter_no?}', 'chapter')->name('page.chapter'); - Route::post('/manga/{manga:slug}/select', 'select')->name('select.chapter'); + Route::get('/MangaDex/manga/{manga:slug}', 'manga')->name('page.manga'); + Route::get('/manga/{manga:slug}/chapter-{chapter:chapter_no?}', 'chapter') + ->name('page.chapter'); + Route::post('/{manga:slug}/chapter', 'select')->name('select.chapter'); }); +Route::resource('comments', CommentController::class)->middleware('auth'); +Route::resource('replies', ReplyController::class)->middleware('auth'); + Auth::routes(); -Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); +Route::get('/home', [App\Http\Controllers\HomeController::class, 'index']) +->name('home'); Route::middleware(['auth', 'admin.access'])->group(function () { Route::resource('manga', MangaController::class); Route::resource('chapter', ChapterController::class); - Route::get('/chapters/manage/{manga:slug}', [ChapterController::class, 'manage'])->name('chapters.manage'); + Route::get('/chapters/manage/{manga:slug}', [ChapterController::class, 'manage']) + ->name('chapters.manage'); Route::get('/users-list', [UserController::class, 'index'])->name('users.list')->middleware('can:admin-only'); }); +