Skip to content

Commit 653efdf

Browse files
committed
Enhance clean events
1 parent 9755b9d commit 653efdf

1 file changed

Lines changed: 72 additions & 41 deletions

File tree

‎app/Console/Commands/CleanDuplicateEvents.php‎

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use Illuminate\Support\Facades\DB;
77

88
/**
9-
* Detect and optionally delete duplicate events (same title, start_date, end_date)
10-
* that have consecutive IDs and were created within <2 seconds.
9+
* Detect and optionally delete duplicate event *chains* in 2025
10+
* (same title, start_date, end_date) created within <2s of each other.
1111
*
1212
* Related pivot tables:
1313
* - event_tag
@@ -27,74 +27,109 @@ class CleanDuplicateEvents extends Command
2727
protected $signature = 'events:clean-duplicates {--delete : Permanently delete detected duplicates and related records from DB}';
2828

2929
/** @var string */
30-
protected $description = 'Detect duplicate events (same title, start_date, end_date, consecutive IDs, created <2s apart) and optionally delete them with related pivot data.';
30+
protected $description = 'Detect duplicate event groups in 2025 (same title/start/end, created <2s apart) and optionally delete them with related pivot data.';
3131

3232
public function handle(): int
3333
{
3434
$shouldDelete = $this->option('delete');
3535

3636
$this->info(sprintf(
37-
'Scanning for possible duplicate events... (mode: %s)',
37+
'Scanning for possible duplicate events in 2025... (mode: %s)',
3838
$shouldDelete ? 'DELETE' : 'DRY-RUN'
3939
));
4040

41-
// --- Detect duplicate event pairs ---
41+
// --- Detect clusters of events with same title/start/end within 2 seconds ---
4242
$duplicates = DB::select("
4343
SELECT
44-
e1.id AS id1,
45-
e2.id AS id2,
46-
e1.title,
47-
e1.start_date,
48-
e1.end_date,
49-
TIMESTAMPDIFF(SECOND, e1.created_at, e2.created_at) AS diff_seconds
50-
FROM events e1
51-
JOIN events e2 ON e2.id = e1.id + 1
52-
WHERE e1.title = e2.title
53-
AND e1.start_date = e2.start_date
54-
AND e1.end_date = e2.end_date
55-
AND ABS(TIMESTAMPDIFF(SECOND, e1.created_at, e2.created_at)) < 2
56-
ORDER BY e1.id
44+
e.id,
45+
e.title,
46+
e.start_date,
47+
e.end_date,
48+
e.created_at,
49+
LAG(e.created_at) OVER (PARTITION BY e.title, e.start_date, e.end_date ORDER BY e.created_at) AS prev_created
50+
FROM events e
51+
WHERE YEAR(e.created_at) = 2025
52+
AND e.title <> ''
53+
AND e.start_date IS NOT NULL
54+
AND e.end_date IS NOT NULL
55+
ORDER BY e.title, e.start_date, e.created_at
5756
");
5857

59-
if (empty($duplicates)) {
60-
$this->info('No duplicate events found.');
58+
$toDelete = [];
59+
$groups = [];
60+
$prevTitle = $prevStart = $prevEnd = null;
61+
$group = [];
62+
63+
foreach ($duplicates as $row) {
64+
$diff = null;
65+
if ($row->prev_created) {
66+
$diff = abs(strtotime($row->created_at) - strtotime($row->prev_created));
67+
}
68+
69+
if (
70+
$row->title === $prevTitle &&
71+
$row->start_date === $prevStart &&
72+
$row->end_date === $prevEnd &&
73+
$diff !== null && $diff < 2
74+
) {
75+
$group[] = $row->id;
76+
} else {
77+
if (count($group) > 1) {
78+
$groups[] = $group;
79+
}
80+
$group = [$row->id];
81+
}
82+
83+
$prevTitle = $row->title;
84+
$prevStart = $row->start_date;
85+
$prevEnd = $row->end_date;
86+
}
87+
88+
if (count($group) > 1) {
89+
$groups[] = $group;
90+
}
91+
92+
if (empty($groups)) {
93+
$this->info('No duplicate event groups found in 2025.');
6194
return self::SUCCESS;
6295
}
6396

64-
$this->info(sprintf('Found %d duplicate pair(s):', count($duplicates)));
97+
$this->info(sprintf('Found %d duplicate group(s):', count($groups)));
6598

6699
$rows = [];
67-
foreach ($duplicates as $row) {
100+
foreach ($groups as $g) {
101+
$firstEvent = collect($duplicates)->firstWhere('id', $g[0]);
102+
68103
$rows[] = [
69-
$row->id1,
70-
$row->id2,
71-
$row->title,
72-
$row->start_date,
73-
$row->end_date,
74-
$row->diff_seconds . 's',
104+
implode(', ', $g),
105+
count($g) . ' items',
106+
$firstEvent?->title ?? '—',
107+
$firstEvent?->start_date ?? '—',
108+
$firstEvent?->end_date ?? '—',
75109
];
110+
111+
$toDelete = array_merge($toDelete, array_slice($g, 1));
76112
}
77113

78-
$this->table(['Event ID A', 'Event ID B', 'Title', 'Start', 'End', 'At'], $rows);
114+
$this->table(
115+
['Event IDs (Group)', 'Count', 'Title', 'Start date', 'End date'],
116+
$rows
117+
);
79118

80119
if (!$shouldDelete) {
81120
$this->newLine();
82-
$this->info('💡 Use --delete to remove duplicates permanently after review.');
121+
$this->info('Use --delete to remove duplicates permanently after review.');
83122
return self::SUCCESS;
84123
}
85124

86125
$confirm = $this->confirm(
87-
sprintf('Are you sure you want to delete %d duplicate entries (including pivot records)?', count($duplicates))
126+
sprintf('Delete %d duplicate event(s) across %d groups (including pivot records)?', count($toDelete), count($groups))
88127
);
89128
if (!$confirm) {
90129
$this->warn('Aborted. No records deleted.');
91130
return self::SUCCESS;
92131
}
93132

94-
// --- Proceed deletion safely ---
95-
$toDelete = array_column($duplicates, 'id2');
96-
$deleted = 0;
97-
98133
$bar = $this->output->createProgressBar(count($toDelete));
99134
$bar->start();
100135

@@ -104,24 +139,20 @@ public function handle(): int
104139
DB::table('event_tag')->where('event_id', $eventId)->delete();
105140
DB::table('event_theme')->where('event_id', $eventId)->delete();
106141
DB::table('audience_event')->where('event_id', $eventId)->delete();
107-
108142
DB::table('events')->where('id', $eventId)->delete();
109-
110-
$deleted++;
111143
$bar->advance();
112144
}
113-
114145
DB::commit();
115146
} catch (\Throwable $e) {
116147
DB::rollBack();
117148
$bar->finish();
118-
$this->error("\nError occurred: " . $e->getMessage());
149+
$this->error("\nError: " . $e->getMessage());
119150
return self::FAILURE;
120151
}
121152

122153
$bar->finish();
123154
$this->newLine(2);
124-
$this->info(sprintf('Successfully deleted %d duplicate event(s) and related pivot records.', $deleted));
155+
$this->info(sprintf('Deleted %d duplicate event(s) and related pivot records.', count($toDelete)));
125156

126157
return self::SUCCESS;
127158
}

0 commit comments

Comments
 (0)