This commit is contained in:
User
2025-01-04 13:02:50 -05:00
parent da807296f3
commit 489e054614
20 changed files with 377 additions and 61 deletions

View 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
View 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;
}
}

View File

@@ -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
*

View File

@@ -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');

View File

@@ -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')
];
}

View File

@@ -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,
];
}
}