88use App \Models \TodoChecklistItem ;
99use App \Models \User ;
1010use Illuminate \Http \Request ;
11+ use Illuminate \Http \UploadedFile ;
1112use Illuminate \Support \Facades \Auth ;
1213use Illuminate \Support \Facades \DB ;
1314use Illuminate \Support \Facades \Storage ;
@@ -191,6 +192,8 @@ public function store(Request $request)
191192
192193 // Handle file attachments
193194 if ($ request ->hasFile ('attachments ' )) {
195+ $ disk = $ this ->attachmentDisk ();
196+
194197 foreach ($ request ->file ('attachments ' ) as $ file ) {
195198 $ originalName = $ file ->getClientOriginalName ();
196199 $ mimeType = $ file ->getMimeType ();
@@ -201,15 +204,20 @@ public function store(Request $request)
201204 $ filePath = "todos/ {$ todo ->id }/attachments/ {$ fileName }" ;
202205
203206 // Store the file
204- Storage::disk ('public ' )->put ($ filePath , file_get_contents ($ file ));
207+ Storage::disk ($ disk )->putFileAs (
208+ "todos/ {$ todo ->id }/attachments " ,
209+ $ file ,
210+ $ fileName ,
211+ $ this ->fsWriteOptions ($ disk )
212+ );
205213
206214 // Determine file type
207215 $ type = TodoAttachment::determineType ($ mimeType );
208216
209217 // Generate thumbnail for images
210218 $ thumbnailPath = null ;
211219 if ($ type === 'image ' ) {
212- $ thumbnailPath = $ this ->generateThumbnail ($ filePath , $ todo ->id );
220+ $ thumbnailPath = $ this ->generateThumbnail ($ file , $ fileName , $ todo ->id , $ disk );
213221 }
214222
215223 // Create attachment record
@@ -404,6 +412,8 @@ public function update(Request $request, Todo $todo)
404412
405413 // Handle new file attachments
406414 if ($ request ->hasFile ('attachments ' )) {
415+ $ disk = $ this ->attachmentDisk ();
416+
407417 foreach ($ request ->file ('attachments ' ) as $ file ) {
408418 $ originalName = $ file ->getClientOriginalName ();
409419 $ mimeType = $ file ->getMimeType ();
@@ -414,15 +424,20 @@ public function update(Request $request, Todo $todo)
414424 $ filePath = "todos/ {$ todo ->id }/attachments/ {$ fileName }" ;
415425
416426 // Store the file
417- Storage::disk ('public ' )->put ($ filePath , file_get_contents ($ file ));
427+ Storage::disk ($ disk )->putFileAs (
428+ "todos/ {$ todo ->id }/attachments " ,
429+ $ file ,
430+ $ fileName ,
431+ $ this ->fsWriteOptions ($ disk )
432+ );
418433
419434 // Determine file type
420435 $ type = TodoAttachment::determineType ($ mimeType );
421436
422437 // Generate thumbnail for images
423438 $ thumbnailPath = null ;
424439 if ($ type === 'image ' ) {
425- $ thumbnailPath = $ this ->generateThumbnail ($ filePath , $ todo ->id );
440+ $ thumbnailPath = $ this ->generateThumbnail ($ file , $ fileName , $ todo ->id , $ disk );
426441 }
427442
428443 // Create attachment record
@@ -760,6 +775,7 @@ public function uploadAttachment(Request $request, Todo $todo)
760775 ]);
761776
762777 $ file = $ request ->file ('file ' );
778+ $ disk = $ this ->attachmentDisk ();
763779 $ originalName = $ file ->getClientOriginalName ();
764780 $ mimeType = $ file ->getMimeType ();
765781 $ fileSize = $ file ->getSize ();
@@ -769,15 +785,20 @@ public function uploadAttachment(Request $request, Todo $todo)
769785 $ filePath = "todos/ {$ todo ->id }/attachments/ {$ fileName }" ;
770786
771787 // Store the file
772- Storage::disk ('public ' )->put ($ filePath , file_get_contents ($ file ));
788+ Storage::disk ($ disk )->putFileAs (
789+ "todos/ {$ todo ->id }/attachments " ,
790+ $ file ,
791+ $ fileName ,
792+ $ this ->fsWriteOptions ($ disk )
793+ );
773794
774795 // Determine file type
775796 $ type = TodoAttachment::determineType ($ mimeType );
776797
777798 // Generate thumbnail for images
778799 $ thumbnailPath = null ;
779800 if ($ type === 'image ' ) {
780- $ thumbnailPath = $ this ->generateThumbnail ($ filePath , $ todo ->id );
801+ $ thumbnailPath = $ this ->generateThumbnail ($ file , $ fileName , $ todo ->id , $ disk );
781802 }
782803
783804 // Create attachment record
@@ -812,12 +833,14 @@ public function deleteAttachment(TodoAttachment $attachment)
812833 }
813834
814835 // Delete files from storage
815- if (Storage::disk ('public ' )->exists ($ attachment ->file_path )) {
816- Storage::disk ('public ' )->delete ($ attachment ->file_path );
836+ $ disk = $ this ->attachmentDisk ();
837+
838+ if (Storage::disk ($ disk )->exists ($ attachment ->file_path )) {
839+ Storage::disk ($ disk )->delete ($ attachment ->file_path );
817840 }
818841
819- if ($ attachment ->thumbnail_path && Storage::disk (' public ' )->exists ($ attachment ->thumbnail_path )) {
820- Storage::disk (' public ' )->delete ($ attachment ->thumbnail_path );
842+ if ($ attachment ->thumbnail_path && Storage::disk ($ disk )->exists ($ attachment ->thumbnail_path )) {
843+ Storage::disk ($ disk )->delete ($ attachment ->thumbnail_path );
821844 }
822845
823846 // Delete attachment record
@@ -836,47 +859,55 @@ public function downloadAttachment(TodoAttachment $attachment)
836859 abort (403 );
837860 }
838861
839- if (! Storage::disk ('public ' )->exists ($ attachment ->file_path )) {
862+ $ disk = $ this ->attachmentDisk ();
863+
864+ if (! Storage::disk ($ disk )->exists ($ attachment ->file_path )) {
840865 abort (404 , 'File not found ' );
841866 }
842867
843- $ filePath = Storage::disk ('public ' )->path ($ attachment ->file_path );
844-
845- return response ()->download ($ filePath , $ attachment ->original_name );
868+ return Storage::disk ($ disk )->download ($ attachment ->file_path , $ attachment ->original_name );
846869 }
847870
848871 /**
849872 * Generate thumbnail for image files.
850873 */
851- private function generateThumbnail (string $ filePath , int $ todoId ): ?string
874+ private function generateThumbnail (UploadedFile $ file , string $ fileName , int $ todoId, string $ disk ): ?string
852875 {
853876 try {
854- $ fullPath = Storage::disk ('public ' )->path ($ filePath );
855- $ thumbnailFileName = 'thumb_ ' .basename ($ filePath );
877+ $ thumbnailFileName = 'thumb_ ' .$ fileName ;
856878 $ thumbnailPath = "todos/ {$ todoId }/thumbnails/ {$ thumbnailFileName }" ;
857- $ thumbnailFullPath = Storage::disk ('public ' )->path ($ thumbnailPath );
858-
859- // Create thumbnails directory if it doesn't exist
860- $ thumbnailDir = dirname ($ thumbnailFullPath );
861- if (! is_dir ($ thumbnailDir )) {
862- mkdir ($ thumbnailDir , 0755 , true );
863- }
879+ $ temporaryThumbnail = tempnam (sys_get_temp_dir (), 'todo-thumb- ' );
864880
865881 // Create thumbnail using intervention/image if available, otherwise use basic PHP
866882 $ imageClass = 'Intervention \\Image \\ImageManagerStatic ' ;
867883 if (class_exists ($ imageClass )) {
868- $ image = $ imageClass ::make ($ fullPath );
884+ $ image = $ imageClass ::make ($ file -> getRealPath () );
869885 $ image ->fit (200 , 200 , function ($ constraint ) {
870886 $ constraint ->upsize ();
871887 });
872- $ image ->save ($ thumbnailFullPath );
888+ $ image ->save ($ temporaryThumbnail );
873889 } else {
874890 // Fallback to basic thumbnail generation
875- $ this ->createBasicThumbnail ($ fullPath , $ thumbnailFullPath );
891+ $ this ->createBasicThumbnail ($ file ->getRealPath (), $ temporaryThumbnail );
892+ }
893+
894+ if (! file_exists ($ temporaryThumbnail )) {
895+ return null ;
876896 }
877897
898+ Storage::disk ($ disk )->put (
899+ $ thumbnailPath ,
900+ file_get_contents ($ temporaryThumbnail ),
901+ $ this ->fsWriteOptions ($ disk )
902+ );
903+ @unlink ($ temporaryThumbnail );
904+
878905 return $ thumbnailPath ;
879906 } catch (\Exception $ e ) {
907+ if (isset ($ temporaryThumbnail ) && file_exists ($ temporaryThumbnail )) {
908+ @unlink ($ temporaryThumbnail );
909+ }
910+
880911 // If thumbnail generation fails, return null
881912 return null ;
882913 }
@@ -942,4 +973,24 @@ private function createBasicThumbnail(string $source, string $destination): void
942973 imagedestroy ($ sourceImage );
943974 imagedestroy ($ thumbnail );
944975 }
976+
977+ private function attachmentDisk (): string
978+ {
979+ return config ('todo.attachments_disk ' , 'public ' );
980+ }
981+
982+ private function fsWriteOptions (string $ disk ): array
983+ {
984+ // With GCS + Uniform Bucket-Level Access, per-object ACLs are not allowed.
985+ if ($ disk === 'gcs ' ) {
986+ return [];
987+ }
988+
989+ return ['visibility ' => $ this ->attachmentVisibility ($ disk )];
990+ }
991+
992+ private function attachmentVisibility (string $ disk ): string
993+ {
994+ return config ("filesystems.disks. {$ disk }.visibility " , 'public ' );
995+ }
945996}
0 commit comments