Fixed error on file upload when folder does not exists

Change-Id: Ic3e503178115f8f6939ae4850d3a240e2f4dda64
This commit is contained in:
Sebastian Marcet 2018-03-13 19:41:30 -03:00
parent 6abd51f8dc
commit 23a16de81d
11 changed files with 230 additions and 37 deletions

View File

@ -12,12 +12,12 @@
* limitations under the License. * limitations under the License.
**/ **/
use App\Events\FileCreated; use App\Events\FileCreated;
use App\Services\Model\IFolderService;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use models\main\File; use models\main\File;
use models\main\IFolderRepository; use models\main\IFolderRepository;
/** /**
* Class FileUploader * Class FileUploader
* @package App\Http\Utils * @package App\Http\Utils
@ -25,12 +25,16 @@ use models\main\IFolderRepository;
final class FileUploader final class FileUploader
{ {
/** /**
* @var IFolderRepository * @var IFolderService
*/ */
private $folder_repository; private $folder_service;
public function __construct(IFolderRepository $folder_repository){ /**
$this->folder_repository = $folder_repository; * FileUploader constructor.
* @param IFolderService $folder_service
*/
public function __construct(IFolderService $folder_service){
$this->folder_service = $folder_service;
} }
/** /**
@ -42,7 +46,8 @@ final class FileUploader
public function build(UploadedFile $file, $folder_name, $is_image = false){ public function build(UploadedFile $file, $folder_name, $is_image = false){
$attachment = new File(); $attachment = new File();
$local_path = Storage::putFileAs(sprintf('/public/%s', $folder_name), $file, $file->getClientOriginalName()); $local_path = Storage::putFileAs(sprintf('/public/%s', $folder_name), $file, $file->getClientOriginalName());
$folder = $this->folder_repository->getFolderByName($folder_name); $folder = $this->folder_service->findOrMake($folder_name);
$attachment->setParent($folder); $attachment->setParent($folder);
$attachment->setName($file->getClientOriginalName()); $attachment->setName($file->getClientOriginalName());
$attachment->setFilename(sprintf("assets/%s/%s",$folder_name, $file->getClientOriginalName())); $attachment->setFilename(sprintf("assets/%s/%s",$folder_name, $file->getClientOriginalName()));

View File

@ -181,10 +181,16 @@ class File extends SilverstripeBaseModel
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->class_name = 'File'; $this->class_name = 'File';
$this->show_in_search = true;
} }
public function setImage(){ public function setImage(){
$this->class_name = 'Image'; $this->class_name = 'Image';
} }
public function setFolder(){
$this->class_name = 'Folder';
}
} }

View File

@ -23,4 +23,18 @@ interface IFolderRepository extends IBaseRepository
* @return File * @return File
*/ */
public function getFolderByName($folder_name); public function getFolderByName($folder_name);
/**
* @param string $file_name
* @return File
*/
public function getFolderByFileName($file_name);
/**
* @param string $folder_name
* @param File $parent
* @return File
*/
public function getFolderByNameAndParent($folder_name, File $parent);
} }

View File

@ -45,6 +45,27 @@ SQL;
return $native_query->getOneOrNullResult(); return $native_query->getOneOrNullResult();
} }
/**
* @param string $file_name
* @return File
*/
public function getFolderByFileName($file_name)
{
$query = <<<SQL
select * from File where ClassName = 'Folder' AND
FileName = :file_name
SQL;
// build rsm here
$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata(\models\main\File::class, 'f');
$native_query = $this->_em->createNativeQuery($query, $rsm);
$native_query->setParameter("file_name", $file_name);
return $native_query->getOneOrNullResult();
}
/** /**
* @return string * @return string
*/ */
@ -52,4 +73,26 @@ SQL;
{ {
return File::class; return File::class;
} }
/**
* @param string $folder_name
* @param File $parent
* @return File
*/
public function getFolderByNameAndParent($folder_name, File $parent)
{
$query = <<<SQL
select * from File where ClassName = 'Folder' AND
Name = :folder_name and ParentID = :parent_id
SQL;
// build rsm here
$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata(\models\main\File::class, 'f');
$native_query = $this->_em->createNativeQuery($query, $rsm);
$native_query->setParameter("folder_name", $folder_name);
$native_query->setParameter("parent_id", $parent->getId());
return $native_query->getOneOrNullResult();
}
} }

View File

@ -0,0 +1,91 @@
<?php namespace App\Services\Model;
/**
* Copyright 2018 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use libs\utils\ITransactionService;
use models\main\File;
use models\main\IFolderRepository;
/**
* Class FolderService
* @package App\Services\Model
*/
final class FolderService implements IFolderService
{
/**
* @var IFolderRepository
*/
private $folder_repository;
/**
* @var ITransactionService
*/
private $tx_service;
/**
* FolderService constructor.
* @param IFolderRepository $folder_repository
* @param ITransactionService $tx_service
*/
public function __construct(IFolderRepository $folder_repository, ITransactionService $tx_service)
{
$this->folder_repository = $folder_repository;
$this->tx_service = $tx_service;
}
/**
* @param string $folder_name
* @return File
*/
public function findOrMake($folder_name)
{
return $this->tx_service->transaction(function() use($folder_name){
$folder = $this->folder_repository->getFolderByFileName($folder_name);
if(!is_null($folder)) return $folder;
// create it
$folder_path = preg_replace('/^\/?(.*)\/?$/', '$1', $folder_name);
$parts = explode("/", $folder_path);
$parent = null;
$item = null;
$file_name = null;
foreach($parts as $part) {
if(!$part) continue; // happens for paths with a trailing slash
if(!empty($file_name))
$file_name .= '/';
$file_name .= $part;
$item = is_null($parent) ?
$this->folder_repository->getFolderByName($part) :
$this->folder_repository->getFolderByNameAndParent($part, $parent);
if(!$item) {
$item = new File();
if(!is_null($parent)){
$item->setParent($parent);
}
else{
$file_name = 'assets/'.$file_name;
}
$item->setFolder();
$item->setName($part);
$item->setTitle($part);
$item->setFilename($file_name);
$this->folder_repository->add($item);
}
$parent = $item;
}
return $item;
});
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Services\Model;
/**
* Copyright 2018 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\main\File;
/**
* Interface IFolderService
* @package App\Services\Model
*/
interface IFolderService
{
/**
* @param $string $folder_name
* @return File
*/
public function findOrMake($folder_name);
}

View File

@ -29,14 +29,12 @@ use App\Models\Foundation\Summit\Factories\SummitLocationBannerFactory;
use App\Models\Foundation\Summit\Factories\SummitLocationFactory; use App\Models\Foundation\Summit\Factories\SummitLocationFactory;
use App\Models\Foundation\Summit\Factories\SummitLocationImageFactory; use App\Models\Foundation\Summit\Factories\SummitLocationImageFactory;
use App\Models\Foundation\Summit\Factories\SummitVenueFloorFactory; use App\Models\Foundation\Summit\Factories\SummitVenueFloorFactory;
use App\Models\Foundation\Summit\Locations\Banners\ScheduledSummitLocationBanner;
use App\Models\Foundation\Summit\Locations\Banners\SummitLocationBanner; use App\Models\Foundation\Summit\Locations\Banners\SummitLocationBanner;
use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository; use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository;
use App\Services\Apis\GeoCodingApiException; use App\Services\Apis\GeoCodingApiException;
use App\Services\Apis\IGeoCodingAPI; use App\Services\Apis\IGeoCodingAPI;
use App\Services\Model\Strategies\GeoLocation\GeoLocationStrategyFactory; use App\Services\Model\Strategies\GeoLocation\GeoLocationStrategyFactory;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use libs\utils\ITransactionService; use libs\utils\ITransactionService;
@ -45,10 +43,7 @@ use models\exceptions\ValidationException;
use models\main\IFolderRepository; use models\main\IFolderRepository;
use models\summit\Summit; use models\summit\Summit;
use models\summit\SummitAbstractLocation; use models\summit\SummitAbstractLocation;
use models\summit\SummitAirport;
use models\summit\SummitExternalLocation;
use models\summit\SummitGeoLocatedLocation; use models\summit\SummitGeoLocatedLocation;
use models\summit\SummitHotel;
use models\summit\SummitLocationImage; use models\summit\SummitLocationImage;
use models\summit\SummitVenue; use models\summit\SummitVenue;
use models\summit\SummitVenueFloor; use models\summit\SummitVenueFloor;
@ -64,11 +59,6 @@ final class LocationService implements ILocationService
*/ */
private $location_repository; private $location_repository;
/**
* @var IFolderRepository
*/
private $folder_repository;
/** /**
* @var ITransactionService * @var ITransactionService
*/ */
@ -79,25 +69,30 @@ final class LocationService implements ILocationService
*/ */
private $geo_coding_api; private $geo_coding_api;
/**
* @var IFolderService
*/
private $folder_service;
/** /**
* LocationService constructor. * LocationService constructor.
* @param ISummitLocationRepository $location_repository * @param ISummitLocationRepository $location_repository
* @param IFolderRepository $folder_repository
* @param IGeoCodingAPI $geo_coding_api * @param IGeoCodingAPI $geo_coding_api
* @param IFolderService $folder_service
* @param ITransactionService $tx_service * @param ITransactionService $tx_service
*/ */
public function __construct public function __construct
( (
ISummitLocationRepository $location_repository, ISummitLocationRepository $location_repository,
IFolderRepository $folder_repository,
IGeoCodingAPI $geo_coding_api, IGeoCodingAPI $geo_coding_api,
IFolderService $folder_service,
ITransactionService $tx_service ITransactionService $tx_service
) )
{ {
$this->location_repository = $location_repository; $this->location_repository = $location_repository;
$this->geo_coding_api = $geo_coding_api; $this->geo_coding_api = $geo_coding_api;
$this->folder_service = $folder_service;
$this->tx_service = $tx_service; $this->tx_service = $tx_service;
$this->folder_repository = $folder_repository;
} }
/** /**
@ -1176,7 +1171,7 @@ final class LocationService implements ILocationService
); );
} }
$uploader = new FileUploader($this->folder_repository); $uploader = new FileUploader($this->folder_service);
$pic = $uploader->build($file, sprintf('summits/%s/locations/%s/maps/', $location->getSummitId(), $location->getId()), true); $pic = $uploader->build($file, sprintf('summits/%s/locations/%s/maps/', $location->getSummitId(), $location->getId()), true);
$map = SummitLocationImageFactory::buildMap($metadata); $map = SummitLocationImageFactory::buildMap($metadata);
$map->setPicture($pic); $map->setPicture($pic);
@ -1280,7 +1275,7 @@ final class LocationService implements ILocationService
); );
} }
$uploader = new FileUploader($this->folder_repository); $uploader = new FileUploader($this->folder_service);
$pic = $uploader->build($file, sprintf('summits/%s/locations/%s/maps/', $location->getSummitId(), $location->getId()), true); $pic = $uploader->build($file, sprintf('summits/%s/locations/%s/maps/', $location->getSummitId(), $location->getId()), true);
$map->setPicture($pic); $map->setPicture($pic);
} }

View File

@ -13,6 +13,7 @@
**/ **/
use App\Models\Foundation\Summit\Factories\PresentationSpeakerSummitAssistanceConfirmationRequestFactory; use App\Models\Foundation\Summit\Factories\PresentationSpeakerSummitAssistanceConfirmationRequestFactory;
use App\Models\Foundation\Summit\Repositories\IPresentationSpeakerSummitAssistanceConfirmationRequestRepository; use App\Models\Foundation\Summit\Repositories\IPresentationSpeakerSummitAssistanceConfirmationRequestRepository;
use App\Services\Model\IFolderService;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use libs\utils\ITransactionService; use libs\utils\ITransactionService;
use models\exceptions\EntityNotFoundException; use models\exceptions\EntityNotFoundException;
@ -20,7 +21,6 @@ use models\exceptions\ValidationException;
use models\main\EmailCreationRequest; use models\main\EmailCreationRequest;
use models\main\File; use models\main\File;
use models\main\IEmailCreationRequestRepository; use models\main\IEmailCreationRequestRepository;
use models\main\IFolderRepository;
use models\main\IMemberRepository; use models\main\IMemberRepository;
use models\main\MemberPromoCodeEmailCreationRequest; use models\main\MemberPromoCodeEmailCreationRequest;
use models\main\SpeakerCreationEmailCreationRequest; use models\main\SpeakerCreationEmailCreationRequest;
@ -52,9 +52,9 @@ final class SpeakerService implements ISpeakerService
private $member_repository; private $member_repository;
/** /**
* @var IFolderRepository * @var IFolderService
*/ */
private $folder_repository; private $folder_service;
/** /**
* @var ISpeakerRegistrationRequestRepository * @var ISpeakerRegistrationRequestRepository
@ -89,7 +89,7 @@ final class SpeakerService implements ISpeakerService
* @param ISpeakerRegistrationRequestRepository $speaker_registration_request_repository * @param ISpeakerRegistrationRequestRepository $speaker_registration_request_repository
* @param ISpeakerSummitRegistrationPromoCodeRepository $registration_code_repository * @param ISpeakerSummitRegistrationPromoCodeRepository $registration_code_repository
* @param IEmailCreationRequestRepository $email_creation_request_repository * @param IEmailCreationRequestRepository $email_creation_request_repository
* @param IFolderRepository $folder_repository * @param IFolderService $folder_service
* @param IPresentationSpeakerSummitAssistanceConfirmationRequestRepository $speakers_assistance_repository * @param IPresentationSpeakerSummitAssistanceConfirmationRequestRepository $speakers_assistance_repository
* @param ITransactionService $tx_service * @param ITransactionService $tx_service
*/ */
@ -100,14 +100,14 @@ final class SpeakerService implements ISpeakerService
ISpeakerRegistrationRequestRepository $speaker_registration_request_repository, ISpeakerRegistrationRequestRepository $speaker_registration_request_repository,
ISpeakerSummitRegistrationPromoCodeRepository $registration_code_repository, ISpeakerSummitRegistrationPromoCodeRepository $registration_code_repository,
IEmailCreationRequestRepository $email_creation_request_repository, IEmailCreationRequestRepository $email_creation_request_repository,
IFolderRepository $folder_repository, IFolderService $folder_service,
IPresentationSpeakerSummitAssistanceConfirmationRequestRepository $speakers_assistance_repository, IPresentationSpeakerSummitAssistanceConfirmationRequestRepository $speakers_assistance_repository,
ITransactionService $tx_service ITransactionService $tx_service
) )
{ {
$this->speaker_repository = $speaker_repository; $this->speaker_repository = $speaker_repository;
$this->member_repository = $member_repository; $this->member_repository = $member_repository;
$this->folder_repository = $folder_repository; $this->folder_service = $folder_service;
$this->speaker_registration_request_repository = $speaker_registration_request_repository; $this->speaker_registration_request_repository = $speaker_registration_request_repository;
$this->registration_code_repository = $registration_code_repository; $this->registration_code_repository = $registration_code_repository;
$this->email_creation_request_repository = $email_creation_request_repository; $this->email_creation_request_repository = $email_creation_request_repository;
@ -397,7 +397,7 @@ final class SpeakerService implements ISpeakerService
throw new ValidationException(sprintf( "file exceeds max_file_size (%s MB).", ($max_file_size/1024)/1024)); throw new ValidationException(sprintf( "file exceeds max_file_size (%s MB).", ($max_file_size/1024)/1024));
} }
$uploader = new FileUploader($this->folder_repository); $uploader = new FileUploader($this->folder_service);
$photo = $uploader->build($file, 'profile-images', true); $photo = $uploader->build($file, 'profile-images', true);
$speaker->setPhoto($photo); $speaker->setPhoto($photo);

View File

@ -17,6 +17,7 @@ use App\Events\MyScheduleAdd;
use App\Events\MyScheduleRemove; use App\Events\MyScheduleRemove;
use App\Http\Utils\FileUploader; use App\Http\Utils\FileUploader;
use App\Models\Utils\IntervalParser; use App\Models\Utils\IntervalParser;
use App\Services\Model\IFolderService;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
@ -25,7 +26,6 @@ use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException; use models\exceptions\ValidationException;
use models\main\File; use models\main\File;
use models\main\ICompanyRepository; use models\main\ICompanyRepository;
use models\main\IFolderRepository;
use models\main\IGroupRepository; use models\main\IGroupRepository;
use models\main\IMemberRepository; use models\main\IMemberRepository;
use models\main\ITagRepository; use models\main\ITagRepository;
@ -135,9 +135,9 @@ final class SummitService implements ISummitService
private $calendar_sync_work_request_repository; private $calendar_sync_work_request_repository;
/** /**
* @var IFolderRepository * @var IFolderService
*/ */
private $folder_repository; private $folder_service;
/** /**
* @var ICompanyRepository * @var ICompanyRepository
@ -161,7 +161,7 @@ final class SummitService implements ISummitService
* @param IRSVPRepository $rsvp_repository * @param IRSVPRepository $rsvp_repository
* @param IAbstractCalendarSyncWorkRequestRepository $calendar_sync_work_request_repository * @param IAbstractCalendarSyncWorkRequestRepository $calendar_sync_work_request_repository
* @param IEventbriteAPI $eventbrite_api * @param IEventbriteAPI $eventbrite_api
* @param IFolderRepository $folder_repository * @param IFolderService $folder_service
* @param ICompanyRepository $company_repository * @param ICompanyRepository $company_repository
* @param IGroupRepository $group_repository, * @param IGroupRepository $group_repository,
* @param ITransactionService $tx_service * @param ITransactionService $tx_service
@ -178,7 +178,7 @@ final class SummitService implements ISummitService
IRSVPRepository $rsvp_repository, IRSVPRepository $rsvp_repository,
IAbstractCalendarSyncWorkRequestRepository $calendar_sync_work_request_repository, IAbstractCalendarSyncWorkRequestRepository $calendar_sync_work_request_repository,
IEventbriteAPI $eventbrite_api, IEventbriteAPI $eventbrite_api,
IFolderRepository $folder_repository, IFolderService $folder_service,
ICompanyRepository $company_repository, ICompanyRepository $company_repository,
IGroupRepository $group_repository, IGroupRepository $group_repository,
ITransactionService $tx_service ITransactionService $tx_service
@ -194,9 +194,9 @@ final class SummitService implements ISummitService
$this->rsvp_repository = $rsvp_repository; $this->rsvp_repository = $rsvp_repository;
$this->calendar_sync_work_request_repository = $calendar_sync_work_request_repository; $this->calendar_sync_work_request_repository = $calendar_sync_work_request_repository;
$this->eventbrite_api = $eventbrite_api; $this->eventbrite_api = $eventbrite_api;
$this->folder_repository = $folder_repository; $this->folder_service = $folder_service;
$this->company_repository = $company_repository; $this->company_repository = $company_repository;
$this->group_repository = $group_repository; $this->group_repository = $group_repository;
$this->tx_service = $tx_service; $this->tx_service = $tx_service;
} }
@ -1230,7 +1230,7 @@ final class SummitService implements ISummitService
throw new ValidationException(sprintf( "file exceeds max_file_size (%s MB).", ($max_file_size/1024)/1024)); throw new ValidationException(sprintf( "file exceeds max_file_size (%s MB).", ($max_file_size/1024)/1024));
} }
$uploader = new FileUploader($this->folder_repository); $uploader = new FileUploader($this->folder_service);
$attachment = $uploader->build($file, 'summit-event-attachments', true); $attachment = $uploader->build($file, 'summit-event-attachments', true);
$event->setAttachment($attachment); $event->setAttachment($attachment);

View File

@ -14,7 +14,9 @@
use App\Services\Apis\GoogleGeoCodingAPI; use App\Services\Apis\GoogleGeoCodingAPI;
use App\Services\Apis\IGeoCodingAPI; use App\Services\Apis\IGeoCodingAPI;
use App\Services\Model\AttendeeService; use App\Services\Model\AttendeeService;
use App\Services\Model\FolderService;
use App\Services\Model\IAttendeeService; use App\Services\Model\IAttendeeService;
use App\Services\Model\IFolderService;
use App\Services\Model\ILocationService; use App\Services\Model\ILocationService;
use App\Services\Model\IMemberService; use App\Services\Model\IMemberService;
use App\Services\Model\ISummitEventTypeService; use App\Services\Model\ISummitEventTypeService;
@ -173,6 +175,12 @@ final class ServicesProvider extends ServiceProvider
LocationService::class LocationService::class
); );
App::singleton
(
IFolderService::class,
FolderService::class
);
App::singleton(IGeoCodingAPI::class, function(){ App::singleton(IGeoCodingAPI::class, function(){
return new GoogleGeoCodingAPI return new GoogleGeoCodingAPI
( (

View File

@ -17,6 +17,11 @@
*/ */
final class OAuth2SummitLocationsApiTest extends ProtectedApiTest final class OAuth2SummitLocationsApiTest extends ProtectedApiTest
{ {
public function testGetFolder(){
$service = \Illuminate\Support\Facades\App::make(\App\Services\Model\IFolderService::class);
$folder = $service->findOrMake('summits/1/locations/292/maps');
}
public function testGetCurrentSummitLocations($summit_id = 23) public function testGetCurrentSummitLocations($summit_id = 23)
{ {
$params = [ $params = [