Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command for cart pruning #1303

Merged
merged 10 commits into from
Jan 15, 2024
30 changes: 30 additions & 0 deletions docs/core/reference/carts.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,33 @@ return [
```

In most cases you won't need to change this.

## Pruning cart data

Over time you will experience a build up of carts in your database that you may want to regularly remove.

You can enable automatic removal of these carts using the `lunar.carts.prune_tables.enabled` config. By setting this to `true` any carts without an order associated will be removed after 90 days.

You can change the number of days carts are retained for using the `lunar.carts.prune_tables. prune_interval` config.

If you have specific needs around pruning you can also change the `lunar.carts.prune_tables.pipelines` array to determine what carts should be removed.



```php
return [
// ...
'prune_tables' => [

'enabled' => false,

'pipelines' => [
Lunar\Pipelines\CartPrune\PruneAfter::class,
Lunar\Pipelines\CartPrune\WithoutOrders::class,
],

'prune_interval' => 90, // days

],
];
```
22 changes: 22 additions & 0 deletions packages/core/config/cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,26 @@
'lines.purchasable.product',
'lines.cart.currency',
],

/*
|--------------------------------------------------------------------------
| Prune carts
|--------------------------------------------------------------------------
|
| Should the cart models be pruned to prevent data build up and
| some settings controlling how pruning should be determined
|
*/
'prune_tables' => [

'enabled' => false,

'pipelines' => [
Lunar\Pipelines\CartPrune\PruneAfter::class,
Lunar\Pipelines\CartPrune\WithoutOrders::class,
],

'prune_interval' => 90, // days

],
];
54 changes: 54 additions & 0 deletions packages/core/src/Console/Commands/PruneCarts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Lunar\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Pipeline\Pipeline;
use Lunar\Models\Cart;

class PruneCarts extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lunar:prune:carts';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Prune the carts table';

/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->info('Beginning prune');

$query = Cart::query();

$carts = app(Pipeline::class)
->send($query)
->through(
config('lunar.cart.prune_tables.pipelines', [])
)->then(function ($query) {
$query->chunk(200, function ($carts) {
$carts->each(function ($cart) {
Cart::where('merged_id', $cart->id)->update(['merged_id' => null]);

$cart->lines()->delete();
$cart->addresses()->delete();
$cart->delete();
});
});
});

$this->info('Prune complete');
}
}
9 changes: 9 additions & 0 deletions packages/core/src/LunarServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Cartalyst\Converter\Laravel\Facades\Converter;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Database\Events\MigrationsStarted;
use Illuminate\Database\Events\NoPendingMigrations;
Expand Down Expand Up @@ -39,6 +40,7 @@
use Lunar\Console\Commands\Import\AddressData;
use Lunar\Console\Commands\MigrateGetCandy;
use Lunar\Console\Commands\Orders\SyncNewCustomerOrders;
use Lunar\Console\Commands\PruneCarts;
use Lunar\Console\Commands\ScoutIndexerCommand;
use Lunar\Console\InstallLunar;
use Lunar\Database\State\ConvertProductTypeAttributesToProducts;
Expand Down Expand Up @@ -204,7 +206,14 @@ public function boot(): void
ScoutIndexerCommand::class,
MigrateGetCandy::class,
SyncNewCustomerOrders::class,
PruneCarts::class,
]);

if (config('lunar.cart.prune_tables.enabled', false)) {
$this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
$schedule->command('lunar:prune:carts')->daily();
});
}
}

Arr::macro('permutate', [\Lunar\Utils\Arr::class, 'permutate']);
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/Pipelines/CartPrune/PruneAfter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Lunar\Pipelines\CartPrune;

use Closure;
use Illuminate\Database\Eloquent\Builder;

final class PruneAfter
{
public function handle(Builder $query, Closure $next)
{
$days = config('lunar.cart.prune_tables.prune_after', 90);

$query->where('updated_at', '<=', now()->subDays($days))
->whereDoesntHave('lines', function ($query) use ($days) {
$query->where('updated_at', '>', now()->subDays($days));
});

return $next($query);
}
}
16 changes: 16 additions & 0 deletions packages/core/src/Pipelines/CartPrune/WithoutOrders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Lunar\Pipelines\CartPrune;

use Closure;
use Illuminate\Database\Eloquent\Builder;

final class WithoutOrders
{
public function handle(Builder $query, Closure $next)
{
$query->whereDoesntHave('orders');

return $next($query);
}
}
45 changes: 45 additions & 0 deletions packages/core/tests/Unit/Console/CartPruneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Lunar\Tests\Unit\Console;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Lunar\Models\Cart;
use Lunar\Models\Channel;
use Lunar\Models\Currency;
use Lunar\Tests\TestCase;

/**
* @group commands
*/
class CartPruneTest extends TestCase
{
use RefreshDatabase;

/** @test */
public function can_prune_carts_with_default_settings()
{
$currency = Currency::factory()->create();
$channel = Channel::factory()->create();

$cart = Cart::create([
'currency_id' => $currency->id,
'channel_id' => $channel->id,
'meta' => ['foo' => 'bar'],
'updated_at' => Carbon::now()->subDay(120),
]);

$cart = Cart::create([
'currency_id' => $currency->id,
'channel_id' => $channel->id,
'meta' => ['foo' => 'bar'],
'updated_at' => Carbon::now()->subDay(20),
]);

$this->assertCount(2, Cart::query()->get());

$this->artisan('lunar:prune:carts');

$this->assertCount(1, Cart::query()->get());
}
}
Loading