99namespace App \Actions \Photo ;
1010
1111use App \Actions \Shop \PurchasableService ;
12+ use App \Assets \Features ;
1213use App \Constants \PhotoAlbum as PA ;
1314use App \DTO \Delete \PhotosToBeDeletedDTO ;
15+ use App \Enum \SizeVariantType ;
1416use App \Events \PhotoDeleted ;
17+ use App \Events \PhotoWillBeDeleted ;
1518use App \Exceptions \Internal \LycheeLogicException ;
1619use App \Exceptions \ModelDBException ;
20+ use App \Models \SizeVariant ;
1721use Illuminate \Support \Facades \DB ;
22+ use Illuminate \Support \Facades \Storage ;
1823
1924/**
2025 * Deletes the photos with the designated IDs **efficiently**.
@@ -108,6 +113,13 @@ public function do(array $photo_ids, string|null $from_id): void
108113
109114 $ this ->purchasable_service ->deleteMulitplePhotoPurchasables ($ photo_ids , [$ from_id ]);
110115
116+ // Fire PhotoWillBeDeleted for each photo that will be hard-deleted,
117+ // BEFORE executeDelete() removes the records from the database.
118+ // Load a lean snapshot of photo data (id, title) and their size variants.
119+ if (count ($ delete_photo_ids ) > 0 ) {
120+ $ this ->dispatchWillBeDeletedEvents ($ delete_photo_ids , $ from_id );
121+ }
122+
111123 $ photos_to_be_deleted = new PhotosToBeDeletedDTO (
112124 force_delete_photo_ids: $ delete_photo_ids ,
113125 soft_delete_photo_ids: $ photo_ids ,
@@ -122,4 +134,56 @@ public function do(array $photo_ids, string|null $from_id): void
122134 dispatch ($ job );
123135 }
124136 }
137+
138+ /**
139+ * Fire PhotoWillBeDeleted for each photo scheduled for hard deletion.
140+ *
141+ * Uses a lean DB query (no full Eloquent hydration) to load photo title
142+ * and size variant URLs before the records are removed.
143+ *
144+ * @param string[] $photo_ids IDs of photos to be hard-deleted
145+ * @param string $album_id the album they are being deleted from
146+ */
147+ private function dispatchWillBeDeletedEvents (array $ photo_ids , string $ album_id ): void
148+ {
149+ // Skip this
150+ if (Features::inactive ('webhook ' )) {
151+ return ;
152+ }
153+
154+ // Load minimal photo data.
155+ $ photos_data = DB ::table ('photos ' )
156+ ->whereIn ('id ' , $ photo_ids )
157+ ->select (['id ' , 'title ' ])
158+ ->get ();
159+
160+ $ photos_data ->chunk (500 )->each (function ($ chunk ) use ($ album_id ): void {
161+ $ photo_ids_chunked = $ chunk ->pluck ('id ' )->all ();
162+ // Load size variant data for all photos in one query.
163+ $ size_variants = SizeVariant::whereIn ('photo_id ' , $ photo_ids_chunked )
164+ ->select (['photo_id ' , 'type ' , 'short_path ' , 'storage_disk ' ])
165+ ->get ()
166+ ->groupBy ('photo_id ' );
167+
168+ foreach ($ chunk as $ photo ) {
169+ $ variants = [];
170+ $ raw_variants = $ size_variants ->get ($ photo ->id , collect ());
171+ /** @var SizeVariant $sv */
172+ foreach ($ raw_variants as $ sv ) {
173+ $ url = $ sv ->type === SizeVariantType::PLACEHOLDER ? $ sv ->short_path : $ sv ->getDownloadUrlAttribute ();
174+ $ variants [] = [
175+ 'type ' => $ sv ->type ->name (),
176+ 'url ' => $ url ,
177+ ];
178+ }
179+
180+ PhotoWillBeDeleted::dispatch (
181+ $ photo ->id ,
182+ $ album_id ,
183+ $ photo ->title ,
184+ $ variants ,
185+ );
186+ }
187+ });
188+ }
125189}
0 commit comments