Skip to content

Commit

Permalink
Merge pull request #27 from ipkpjersi/develop
Browse files Browse the repository at this point in the history
Added other anime on anime details page, Improved duplicate anime checking, other fixes and styling improvements
  • Loading branch information
ipkpjersi authored Jul 14, 2024
2 parents e016000 + 2a396b0 commit 71b179c
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 12 deletions.
47 changes: 45 additions & 2 deletions app/Http/Controllers/AnimeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Services\AnimeListExportService;
use App\Services\AnimeListImportService;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
Expand Down Expand Up @@ -131,14 +132,16 @@ public function detail($id, $title = null)
})
->where('users.show_reviews_publicly', true)
->where('users.is_banned', false)
->where('anime_reviews.is_deleted', 0)
->latest('anime_reviews.created_at')
->paginate(2, ['anime_reviews.*', 'users.username', 'users.avatar', 'users.id as user_id'], 'reviewpage')
->withQueryString(); //We could manually appends() instead of using withQueryString() but withQueryString() is simpler.

$totalReviewsCount = AnimeReview::where('anime_id', $id)
->join('users', 'anime_reviews.user_id', '=', 'users.id')
->where('anime_reviews.show_review_publicly', true)
->where('users.show_reviews_publicly', true)
->where('anime_reviews.show_review_publicly', true)
->where('anime_reviews.is_deleted', 0)
->where('users.is_banned', false)->count();

$aatScore = DB::table('anime_user')
Expand All @@ -157,7 +160,47 @@ public function detail($id, $title = null)
->whereNotNull('score')
->count();

return view('animedetail', compact('anime', 'watchStatuses', 'currentUserStatus', 'currentUserProgress', 'currentUserScore', 'currentUserSortOrder', 'currentUserNotes', 'currentUserDisplayInList', 'currentUserShowAnimeNotesPublicly', 'reviews', 'userHasReview', 'userReview', 'totalReviewsCount', 'aatScore', 'aatMembers', 'aatUsers'));
$otherAnimeTags = [
'Action', 'Adventure', 'Comedy', 'Drama', 'Fantasy', 'Horror', 'Mystery', 'Romance', 'Sci-Fi',
'Slice of Life', 'Supernatural', 'Thriller', 'Romantic Comedy', 'Coming of Age', 'School', 'Sports',
'Magic', 'Military', 'Mecha', 'Music', 'Historical', 'Psychological', 'Battle Royale',
'Isekai', 'Post-Apocalyptic', 'Space', 'Time Travel', 'Virtual Reality', 'Superpower', 'Cyberpunk',
'Mystery', 'Harem', 'Reverse Harem', 'Tsundere', 'Yandere', 'Parody', "Ojou-Sama", "Maids"
];

$currentAnimeTags = array_map('strtolower', explode(', ', $anime->tags));
$otherAnimeTags = array_map('strtolower', $otherAnimeTags);
$filteredTags = array_intersect($currentAnimeTags, $otherAnimeTags);
$otherAnime = [];
if (!empty($filteredTags)) {
$tagConditions = array_map(function($tag) {
return "tags LIKE '%" . $tag . "%'";
}, $filteredTags);

$tagConditions = implode(' OR ', $tagConditions);

$otherAnime = DB::table('anime')
->select('anime.*', DB::raw("(
" . implode(' + ', array_map(function($tag) {
return "IF(tags LIKE '%" . $tag . "%', 1, 0)";
}, $filteredTags)) . "
) as match_count"))
->where('id', '!=', $id)
->whereRaw("($tagConditions)")
->orderBy('match_count', 'desc')
->limit(500)
->get();

$page = LengthAwarePaginator::resolveCurrentPage('otheranimepage');
$perPage = 5;
$offset = ($page * $perPage) - $perPage;
$paginatedItems = $otherAnime->slice($offset, $perPage)->values();
$otherAnime = new LengthAwarePaginator($paginatedItems, $otherAnime->count(), $perPage, $page, [
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => 'otheranimepage',
]);
}
return view('animedetail', compact('anime', 'watchStatuses', 'currentUserStatus', 'currentUserProgress', 'currentUserScore', 'currentUserSortOrder', 'currentUserNotes', 'currentUserDisplayInList', 'currentUserShowAnimeNotesPublicly', 'reviews', 'userHasReview', 'userReview', 'totalReviewsCount', 'aatScore', 'aatMembers', 'aatUsers', 'otherAnime'));
}

public function addReview(Request $request)
Expand Down
26 changes: 26 additions & 0 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ public function detail(Request $request, $username)
})
->where('users.show_reviews_publicly', true)
->where('users.is_banned', false)
->where('anime_reviews.is_deleted', 0)
->latest('anime_reviews.created_at')
->paginate(2, ['anime_reviews.*', 'users.username', 'users.avatar', 'users.id as user_id'], 'reviewpage');

$totalReviewsCount = AnimeReview::where('user_id', $user->id)
->join('users', 'anime_reviews.user_id', '=', 'users.id')
->where('anime_reviews.show_review_publicly', true)
->where('users.show_reviews_publicly', true)
->where('anime_reviews.is_deleted', 0)
->where('users.is_banned', false)->count();
$friendUser = null;
if (! $isOwnProfile && $currentUser) {
Expand Down Expand Up @@ -152,6 +154,30 @@ public function unbanUser(Request $request, $userId)
return response()->json(['message' => 'User unbanned successfully']);
}

public function removeReview(Request $request, $reviewId)
{
// Ensure only admins can remove reviews
if (!auth()->user()->isAdmin()) {
return response()->json(['message' => 'Unauthorized'], 403);
}

$review = AnimeReview::findOrFail($reviewId);
$review->is_deleted = 1;
$review->save();

$anime = $review->anime()->first();
$user = $review->user()->first();

StaffActionLog::create([
'user_id' => auth()->id(),
'target_id' => $review->user_id,
'action' => 'remove_review',
'message' => 'Removed review of anime ' . $anime->title . ' (anime ID: ' . $anime->id . ') from user: ' . $user->username . ' (user ID: ' . $review->user_id . ')'
]);

return response()->json(['message' => 'Review removed successfully']);
}

public function removeAvatar(Request $request, $userId)
{
if (auth()->user() == null || ! auth()->user()->isModerator()) {
Expand Down
35 changes: 31 additions & 4 deletions app/Services/DuplicateAnimeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,21 @@ public function exportDuplicatesToCSV($logger = null)

private function exportDuplicateCounts($date, $logger)
{
$duplicates = DB::table('anime')
$titleDuplicates = DB::table('anime')
->select('title', DB::raw('COUNT(*) as occurrences'))
->groupBy('title')
->havingRaw('COUNT(*) > 1')
->get();
//Technically, duplicate pictures can generate false positives, like 139 entries with default spring picture would mean they aren't duplicates, but if there's two or three or four of the same picture, that's very likely a duplicate entry.
$pictureDuplicates = DB::table('anime')
->select('picture', DB::raw('COUNT(*) as occurrences'))
->whereNotNull('picture')
->where('picture', '!=', '')
->groupBy('picture')
->havingRaw('COUNT(*) > 1')
->get();

$duplicates = $titleDuplicates->merge($pictureDuplicates);

return $this->saveToCsv($duplicates, "duplicate_counts_{$date}.csv", $logger);
}
Expand All @@ -48,6 +58,14 @@ private function exportTotalDuplicates($date, $logger)
->groupBy('title')
->havingRaw('COUNT(*) > 1');
})
->orWhereIn('picture', function ($query) {
$query->select('picture')
->from('anime')
->whereNotNull('picture')
->where('picture', '!=', '')
->groupBy('picture')
->havingRaw('COUNT(*) > 1');
})
->count();

$totalDuplicatesData = new \stdClass();
Expand All @@ -58,13 +76,22 @@ private function exportTotalDuplicates($date, $logger)

private function exportAllDuplicateDetails($date, $logger)
{
//Technically, duplicate pictures can generate false positives, like 139 entries with default spring picture would mean they aren't duplicates, but if there's two or three or four of the same picture, that's very likely a duplicate entry.
$duplicates = DB::table('anime')
->whereIn('title', function ($query) {
$query->select('title')
->from('anime')
->groupBy('title')
->havingRaw('COUNT(*) > 1');
})
->orWhereIn('picture', function ($query) {
$query->select('picture')
->from('anime')
->whereNotNull('picture')
->where('picture', '!=', '')
->groupBy('picture')
->havingRaw('COUNT(*) > 1');
})
->orderBy('title')
->get();

Expand All @@ -75,13 +102,13 @@ private function saveToCsv($data, $filename, $logger)
{
$csv = Writer::createFromString('');

// Check if $data is a Collection or an array
//Check if $data is a Collection or an array
$firstRecord = is_array($data) ? reset($data) : $data->first();

// Convert the first record to an array and insert the keys as the first row in the CSV
//Convert the first record to an array and insert the keys as the first row in the CSV
$csv->insertOne(array_keys((array) $firstRecord));

// Iterate over each record and insert into CSV
//Iterate over each record and insert into CSV
foreach ($data as $record) {
$csv->insertOne((array) $record);
}
Expand Down
2 changes: 1 addition & 1 deletion app/Support/Google2FAAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Google2FAAuthenticator extends Authenticator
{
protected function canPassWithoutCheckingOTP()
{
if ($this->getUser()->passwordSecurity == null) {
if ($this->getUser() == null || $this->getUser()->passwordSecurity == null) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('anime_reviews', function (Blueprint $table) {
$table->boolean('is_deleted')->default(false);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('anime_reviews', function (Blueprint $table) {
$table->dropColumn('is_deleted');
});
}
};
47 changes: 43 additions & 4 deletions resources/views/animedetail.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@
<div><a href="{{ route('anime.top', ['sort' => 'most_popular']) }}"><strong>MAL Popularity:</strong> {{ ($anime->mal_popularity ?? 0) > 0 ? '#' . number_format($anime->mal_popularity) : 'N/A' }}</a></div>

<div><strong>MAL Members:</strong> {{ ($anime->mal_list_members ?? 0) > 0 ? number_format($anime->mal_list_members) : 'N/A' }}</div>
<div><strong>AAT Score:</strong> {{ ($aatScore ?? 0) > 0 ? $aatScore : "N/A" }}</div>
<div><strong>AAT Score:</strong> {{ ($aatScore ?? 0) > 0 ? number_format($aatScore, 2) : "N/A" }}</div>
<div><strong>AAT Members:</strong> {{ ($aatMembers ?? 0) > 0 ? $aatMembers : "N/A" }}</div>
<div><strong>AAT Users:</strong> {{ ($aatUsers ?? 0) > 0 ? $aatUsers : "N/A" }}</div>

@if (Auth::user() !== null)
<div><strong>My Score:</strong> {{ $currentUserScore > 0 ? number_format($currentUserScore) : 'N/A' }}</div>
<div><strong>My Score:</strong> {{ $currentUserScore > 0 ? number_format($currentUserScore, 2) : 'N/A' }}</div>
<div><strong>My Status:</strong> {{ $currentUserStatus > 0 ? $watchStatuses[$currentUserStatus]->status ?? "N/A" : "N/A" }}</div>
@endif
</div>
Expand All @@ -211,19 +211,36 @@
<p class="mb-4">{!! str_replace("\n", "<br>", empty(trim($anime->description)) ? "This title does not have a description yet." : $anime->description) !!}</p>

<h4 class="font-bold @if (!empty(trim($anime->description))) mt-4 @endif mb-2">More Details:</h4>
<ul>
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-4">
@foreach (explode(', ', $anime->sources) as $source)
<li><a href="{{ $source }}" target="_blank" rel="noopener">{{ $source }}</a></li>
@endforeach
</ul>

<h4 class="font-bold mt-4 mb-2">Related Anime:</h4>
<ul>
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-4">
@foreach (explode(', ', $anime->relations) as $relation)
<li><a href="{{ $relation }}" target="_blank" rel="noopener">{{ $relation }}</a></li>
@endforeach
</ul>

@if (!empty($otherAnime))
<h4 class="font-bold mt-4 mb-2">Other Anime:</h4>
<div class="flex flex-wrap -mx-2" id="other-anime-list">
@foreach ($otherAnime as $anime)
<div class="w-1/2 md:w-1/5 px-2 mb-4">
<a href="/anime/{{ $anime->id }}/{{ Str::slug($anime->title) }}" class="block border p-2 h-full">
<div class="h-full flex flex-col items-center">
<img src="{{ $anime->thumbnail }}" onerror="this.onerror=null; this.src='/img/notfound.gif';" alt="{{ $anime->title }}" class="h-16 w-12 mb-2">
<h5 class="text-center">{{ Str::limit($anime->title, 40) }}</h5>
</div>
</a>
</div>
@endforeach
</div>
{{ $otherAnime->links() }}
@endif

<!-- Anime Reviews Section -->
@if (auth()->user() === null || auth()->user()->show_others_reviews === 1)
<div class="mt-8">
Expand Down Expand Up @@ -273,6 +290,10 @@
@endswitch
</p>
<p class="mt-1"><strong>By:</strong> <a href="{{route('users.detail', $review->user->username)}}"><img src="{{ $review->user->avatar ?? '/img/default-avatar.png' }}" alt="Avatar" style="width:50px; max-height:70px" onerror="this.onerror=null; this.src='/img/notfound.gif';"/> {{ $review->user->username }} on {{ $review->created_at->format('M d, Y H:i:s A') }}</a></p>
<!-- Remove Review Button -->
@if (auth()->user() && auth()->user()->isAdmin())
<button data-review-id="{{ $review->id }}" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded removeReview mt-2 mb-3">Remove Review</button>
@endif
</div>
@empty
<p>No reviews available.</p>
Expand Down Expand Up @@ -371,6 +392,9 @@
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (window.location.search.includes('otheranimepage')) {
document.getElementById('other-anime-list').scrollIntoView({ behavior: 'smooth' });
}
// Check if the element exists
const statusModal = document.getElementById('status-modal');
if (statusModal) {
Expand Down Expand Up @@ -409,4 +433,19 @@ function toggleReviewContent(reviewId) {
}
}
</script>
<script type="module">
$(document).on('click', '.removeReview', function() {
let reviewId = $(this).data('review-id');
axios.post(`/reviews/${reviewId}/remove`, {
_token: '{{ csrf_token() }}'
})
.then(function(response) {
//alert(response.data.message);
location.reload();
})
.catch(function(error) {
alert('Error removing review: ' + error);
});
});
</script>
</x-app-layout>
Loading

0 comments on commit 71b179c

Please sign in to comment.