* Irreversibly deletes every Product and everything that only exists
* because of a product — variants, variant prices, product-option value
* assignments, product images/media, product associations, the
* ImportMapping rows tying them back to an external source, and the
* Meilisearch product index. Deliberately does NOT touch catalog
* STRUCTURE other products could still reference: ProductOption/
* ProductOptionValue definitions ("Size", "Color" as reusable option
* types), Brands, Collections, Tags, Customer Groups — none of those are
* products, they're config a merchant would otherwise have to rebuild
* from scratch.
*
* Two gates a destructive, whole-catalog, irreversible operation
* warrants — deliberately NOT restricted to non-production on top of
* these; a real, legitimate use case is wiping a client's demo/seed
* catalog on a production database right before real launch, and the OTP
* below already proves the operator has real staff access, not just
* shell access to wherever `php artisan` happens to be runnable:
* 1. An OTP emailed to a real Staff account (reusing Auth\Services\
* OtpService — the exact mechanism admin login already uses).
* 2. Typing the literal product count back, not just "yes" — a plain
* confirm() is too easy to reflexively accept; forcing the operator
* to read and retype the actual number they're about to delete is a
* last check against running this against the wrong environment/
* database by mistake.
*
* Deletes via Eloquent model instances, not DB::table()->delete() —
* Product/ProductVariant use Spatie's InteractsWithMedia (see Lunar\Base\
* Traits\HasMedia), which only cleans up media files/rows on a real model
* `deleted` event, never on a raw query-builder delete.
*/
classWipeCatalogCommandextendsCommand
{
protected$signature='boboko:wipe-catalog {--email= : Staff email to send the confirmation code to}';
protected$description='Irreversibly delete every product, variant, and related catalog data';
publicfunctionhandle(OtpService$otp):int
{
$productCount=Product::count();
if($productCount===0){
$this->info('No products exist — nothing to do.');
returnself::SUCCESS;
}
if(!$this->authorize($otp)){
returnself::FAILURE;
}
$this->warn("This will PERMANENTLY delete {$productCount} product(s) and everything that only exists because of them (variants, prices, images, product-option assignments, associations). This cannot be undone.");
$typed=text(label:"Type the product count ({$productCount}) to confirm");
if($typed!==(string)$productCount){
$this->error('Count did not match — aborted, nothing was deleted.');
returnself::FAILURE;
}
$this->wipe();
$this->info("Deleted {$productCount} product(s) and all related data.");
returnself::SUCCESS;
}
privatefunctionauthorize(OtpService$otp):bool
{
$email=$this->option('email')??text(
label:'Staff email to send a confirmation code to',