0.1.0
This commit is contained in:
77
app/Console/Commands/CleanupReadingHistories.php
Normal file
77
app/Console/Commands/CleanupReadingHistories.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
|
||||
class CleanupReadingHistories extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cleanup:histories
|
||||
{userIds?* : The user ID(s) to clean up (use "all" to clean up for all users)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clean up duplicate reading histories for specified or all users';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$userIds = $this->argument('userIds');
|
||||
|
||||
if (empty($userIds)) {
|
||||
$this->error('You must specify user IDs or "all".');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Handle "all" case
|
||||
if ($userIds === ['all']) {
|
||||
$users = User::all();
|
||||
foreach ($users as $user) {
|
||||
$this->cleanUpForUser($user);
|
||||
}
|
||||
} else {
|
||||
// Ensure user IDs are unique
|
||||
$uniqueUserIds = array_unique($userIds);
|
||||
|
||||
foreach ($uniqueUserIds as $userId) {
|
||||
$user = User::find($userId);
|
||||
if ($user) {
|
||||
$this->cleanUpForUser($user);
|
||||
} else {
|
||||
$this->error("User with ID {$userId} not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Cleanup completed successfully.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up duplicate reading histories for a single user.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return void
|
||||
*/
|
||||
protected function cleanUpForUser(User $user): void
|
||||
{
|
||||
$result = $user->cleanUpReadingHistories();
|
||||
|
||||
$keptIds = $result['kept_ids']->toArray(); // Convert Collection to array
|
||||
|
||||
$this->info("User ID {$result['user_id']} - Kept IDs: " . implode(', ', $keptIds) . " - Deleted: {$result['deleted_count']} records.");
|
||||
}
|
||||
}
|
||||
41
app/Helper/Timezones.php
Normal file
41
app/Helper/Timezones.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helper;
|
||||
|
||||
use DateTimeZone;
|
||||
|
||||
class Timezones
|
||||
{
|
||||
|
||||
public function groups(): array
|
||||
{
|
||||
return ['Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Australia', 'Europe', 'Indian', 'Pacific', 'UTC'];
|
||||
}
|
||||
|
||||
public function locations(string $group): array
|
||||
{
|
||||
return DateTimeZone::listIdentifiers(DateTimeZone::{strtoupper($group)});
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$tzForSelect = [];
|
||||
|
||||
foreach (DateTimeZone::listIdentifiers() as $timezone) {
|
||||
$tz = explode('/', $timezone);
|
||||
if (!isset($tz[1])) {
|
||||
$tzForSelect['UTC']['UTC'] = 'UTC';
|
||||
} else {
|
||||
if (isset($tz[2])) {
|
||||
$tzForSelect[$tz[0]][$tz[1]] = $tz[1]."/".$tz[2];
|
||||
} else {
|
||||
$tzForSelect[$tz[0]][$timezone] = $tz[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tzForSelect;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -361,7 +361,7 @@ class ComicController extends Controller
|
||||
{
|
||||
// Get history
|
||||
$histories = $request->user()->readingHistories()->with(['comic:id,name,pathword'])->orderByDesc('reading_histories.created_at')
|
||||
->select(['reading_histories.id as hid', 'reading_histories.created_at as read_at', 'chapters.comic_id', 'chapters.name'])->paginate(50)->toArray();
|
||||
->select(['reading_histories.id as hid', 'chapters.comic_id', 'chapters.name'])->paginate(50)->toArray();
|
||||
|
||||
return Inertia::render('Comic/Histories', [
|
||||
'histories' => $this->scToZh($histories)
|
||||
@@ -374,7 +374,7 @@ class ComicController extends Controller
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function destroyHistories(Request $request): RedirectResponse
|
||||
public function patchHistories(Request $request): RedirectResponse
|
||||
{
|
||||
if (!is_array($request->get('ids')) && $request->get('ids') === 'all') {
|
||||
$histories = $request->user()->readingHistories()->delete();
|
||||
@@ -385,6 +385,27 @@ class ComicController extends Controller
|
||||
return redirect()->route('comics.histories');
|
||||
}
|
||||
|
||||
public function destroyHistory(Request $request, string $pathword): Response
|
||||
{
|
||||
$comicId = Comic::where('pathword', $pathword)->firstOrFail(['id'])->id;
|
||||
$request->user()->readingHistories()->where('reading_histories.comic_id', $comicId)->delete();
|
||||
|
||||
// Get history
|
||||
$histories = $request->user()->readingHistories()->where('reading_histories.comic_id', $comicId)
|
||||
->distinct()->select('chapter_uuid')->get()->pluck('chapter_uuid');
|
||||
|
||||
return Inertia::render('Comic/Chapters', [
|
||||
'histories' => $histories
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroyHistories(Request $request): RedirectResponse
|
||||
{
|
||||
$result = $request->user()->cleanUpReadingHistories();
|
||||
|
||||
return redirect()->route('comics.histories');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tags
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helper\Timezones;
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -18,8 +19,11 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$tz = new Timezones();
|
||||
|
||||
return Inertia::render('Profile/Edit', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'timezones' => $tz->toArray(),
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
@@ -35,6 +39,12 @@ class ProfileController extends Controller
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
// Settings
|
||||
$settings = $request->user()->settings;
|
||||
$settings['timezone'] = $request->get('timezone');
|
||||
$request->user()->settings = $settings;
|
||||
$request->user()->save();
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit');
|
||||
|
||||
@@ -18,6 +18,7 @@ class UserCollection extends ResourceCollection
|
||||
'id' => $request->user()->id,
|
||||
'name' => $request->user()->name,
|
||||
'email' => $request->user()->email,
|
||||
'settings' => $request->user()->settings,
|
||||
'favourites' => $request->user()->favourites()->get(['pathword'])->pluck('pathword')
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
@@ -24,6 +24,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'settings',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,10 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'settings' => "{timezone:'UTC'}",
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
@@ -46,6 +51,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'settings' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -59,4 +65,41 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $this->belongsToMany(Chapter::class, 'reading_histories')->withTimestamps();
|
||||
}
|
||||
|
||||
public function cleanUpReadingHistories(): array
|
||||
{
|
||||
// Get the user's ID
|
||||
$userId = $this->id;
|
||||
|
||||
// Step 1: Identify records to keep
|
||||
$idsToKeep = ReadingHistory::query()
|
||||
->select('reading_histories.id') // Specify table name explicitly
|
||||
->joinSub(
|
||||
ReadingHistory::query()
|
||||
->select('comic_id', 'user_id', 'chapter_id', DB::raw('MIN(created_at) as earliest_created_at'))
|
||||
->where('user_id', $userId) // Specify user_id with table name
|
||||
->groupBy('comic_id', 'user_id', 'chapter_id'),
|
||||
'b',
|
||||
function (JoinClause $join) {
|
||||
$join->on('reading_histories.comic_id', '=', 'b.comic_id')
|
||||
->on('reading_histories.user_id', '=', 'b.user_id') // Disambiguate user_id
|
||||
->on('reading_histories.chapter_id', '=', 'b.chapter_id')
|
||||
->on('reading_histories.created_at', '=', 'b.earliest_created_at');
|
||||
}
|
||||
)
|
||||
->where('reading_histories.user_id', $userId) // Specify table name explicitly
|
||||
->pluck('id');
|
||||
|
||||
// Step 2: Delete duplicates for the user
|
||||
$deletedCount = ReadingHistory::where('user_id', $userId)
|
||||
->whereNotIn('id', $idsToKeep)
|
||||
->delete();
|
||||
|
||||
// Return the result as an array
|
||||
return [
|
||||
'user_id' => $userId,
|
||||
'kept_ids' => $idsToKeep,
|
||||
'deleted_count' => $deletedCount,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user