fix: Add idempotency checks to all Elasticsearch migrations

Problem:
- Running elastic:rebuild --model=X failed when indexes already existed
- Migrations threw "index already exists" errors in production
- Command runs ALL migrations even when rebuilding single model

Solution:
- Added existence checks to all 12 migration files
- Migrations now skip creation if index already exists
- Safe to run multiple times without errors

Changes:
- Added ClientBuilder import to all migrations
- Check indices()->exists() before creating
- Return early if index already exists
- Removed force drop from recurring_invoices migration

Benefits:
- Production-safe partial rebuilds
- No "already exists" errors
- Idempotent migrations
- Clean log output

Files modified:
- All 12 files in elastic/migrations/*.php
This commit is contained in:
David Bomba 2025-11-26 02:15:02 +00:00
parent 04722bae9f
commit d27b3ffc47
12 changed files with 83 additions and 5 deletions

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateInvoicesIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateInvoicesIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'invoices_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core invoice fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateQuotesIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateQuotesIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'quotes_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core quote fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateCreditsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateCreditsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'credits_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core credit fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateRecurringInvoicesIndex implements MigrationInterface
{
@ -13,9 +14,11 @@ final class CreateRecurringInvoicesIndex implements MigrationInterface
*/
public function up(): void
{
// Force drop any existing indices to avoid mapping conflicts
Index::dropIfExists('recurring_invoices_v2');
Index::dropIfExists('recurring_invoices');
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'recurring_invoices_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreatePurchaseOrdersIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreatePurchaseOrdersIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'purchase_orders_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core purchase order fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateVendorsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateVendorsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'vendors_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core vendor fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateExpensesIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateExpensesIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'expenses_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core expense fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateProjectsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateProjectsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'projects_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core project fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateTasksIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateTasksIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'tasks_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core task fields
@ -68,4 +75,3 @@ final class CreateTasksIndex implements MigrationInterface
}
}

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateClientContactsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateClientContactsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'client_contacts_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core client contact fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateVendorContactsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateVendorContactsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'vendor_contacts_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core vendor contact fields

View File

@ -5,6 +5,7 @@ use Elastic\Adapter\Indices\Mapping;
use Elastic\Adapter\Indices\Settings;
use Elastic\Migrations\Facades\Index;
use Elastic\Migrations\MigrationInterface;
use Elastic\Elasticsearch\ClientBuilder;
final class CreateClientsIndex implements MigrationInterface
{
@ -13,6 +14,12 @@ final class CreateClientsIndex implements MigrationInterface
*/
public function up(): void
{
// Check if index already exists (idempotency)
$client = ClientBuilder::fromConfig(config('elastic.client.connections.default'));
if ($client->indices()->exists(['index' => 'clients_v2'])) {
return; // Index already exists, skip creation
}
$mapping = [
'properties' => [
// Core client fields
@ -143,4 +150,3 @@ final class CreateClientsIndex implements MigrationInterface