Skip to content

Commit

Permalink
[User Management] Upload user image endpoint (#563)
Browse files Browse the repository at this point in the history
* Refactor RequestBody to OpenAPI namespace.

* Add upload user image endpoint.

* Add Image UploadService Test.

* Translation.

* Apply php-cs-fixer changes

* Remove unused service.

* Apply php-cs-fixer changes

---------

Co-authored-by: martineiber <[email protected]>
  • Loading branch information
martineiber and martineiber authored Nov 21, 2024
1 parent 04e5665 commit 47dec90
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 10 deletions.
4 changes: 4 additions & 0 deletions config/users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ services:
Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingServiceInterface:
class: Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingService

Pimcore\Bundle\StudioBackendBundle\User\Service\ImageUploadServiceInterface:
class: Pimcore\Bundle\StudioBackendBundle\User\Service\ImageUploadService



Pimcore\Bundle\StudioBackendBundle\User\Service\MailServiceInterface:
class: Pimcore\Bundle\StudioBackendBundle\User\Service\MailService
Expand Down
4 changes: 2 additions & 2 deletions src/Asset/Controller/Upload/AddController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
use League\Flysystem\FilesystemException;
use OpenApi\Attributes\Post;
use OpenApi\Attributes\Property;
use Pimcore\Bundle\StudioBackendBundle\Asset\Attribute\Request\AddAssetRequestBody;
use Pimcore\Bundle\StudioBackendBundle\Asset\Service\UploadServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\AccessDeniedException;
Expand All @@ -29,6 +28,7 @@
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request\MultipartFormDataRequestBody;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\IdJson;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
Expand Down Expand Up @@ -80,7 +80,7 @@ public function __construct(
content: new IdJson('ID of created asset')
)]
#[IdParameter(type: ElementTypes::TYPE_ASSET, name: 'parentId')]
#[AddAssetRequestBody(
#[MultipartFormDataRequestBody(
[
new Property(
property: 'file',
Expand Down
4 changes: 2 additions & 2 deletions src/Asset/Controller/Upload/ReplaceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

use OpenApi\Attributes\Post;
use OpenApi\Attributes\Property;
use Pimcore\Bundle\StudioBackendBundle\Asset\Attribute\Request\AddAssetRequestBody;
use Pimcore\Bundle\StudioBackendBundle\Asset\Service\UploadServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\AccessDeniedException;
Expand All @@ -29,6 +28,7 @@
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request\MultipartFormDataRequestBody;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
Expand Down Expand Up @@ -77,7 +77,7 @@ public function __construct(
description: 'asset_replace_success_response',
)]
#[IdParameter(type: ElementTypes::TYPE_ASSET)]
#[AddAssetRequestBody(
#[MultipartFormDataRequestBody(
[
new Property(
property: 'file',
Expand Down
4 changes: 2 additions & 2 deletions src/Asset/Controller/Upload/ZipController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

use OpenApi\Attributes\Post;
use OpenApi\Attributes\Property;
use Pimcore\Bundle\StudioBackendBundle\Asset\Attribute\Request\AddAssetRequestBody;
use Pimcore\Bundle\StudioBackendBundle\Asset\Service\ExecutionEngine\ZipServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\AccessDeniedException;
Expand All @@ -27,6 +26,7 @@
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request\MultipartFormDataRequestBody;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\IdJson;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\CreatedResponse;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
Expand Down Expand Up @@ -78,7 +78,7 @@ public function __construct(
content: new IdJson('ID of created jobRun', 'jobRunId')
)]
#[IdParameter(type: ElementTypes::TYPE_ASSET, name: 'parentId')]
#[AddAssetRequestBody(
#[MultipartFormDataRequestBody(
[
new Property(
property: self::FILE_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Asset\Attribute\Request;
namespace Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request;

use Attribute;
use OpenApi\Attributes\MediaType;
Expand All @@ -25,7 +25,7 @@
* @internal
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class AddAssetRequestBody extends RequestBody
final class MultipartFormDataRequestBody extends RequestBody
{
public function __construct(array $properties, array $required = [])
{
Expand Down
2 changes: 1 addition & 1 deletion src/User/Controller/UpdateUserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function __construct(
/**
* @throws NotFoundException|DatabaseException|ForbiddenException|ParseException
*/
#[Route('/user/{id}', name: 'pimcore_studio_api_user_update', methods: ['PUT'])]
#[Route('/user/{id}', name: 'pimcore_studio_api_user_update', requirements: ['id' => '\d+'], methods: ['PUT'])]
#[IsGranted(UserPermissions::USER_MANAGEMENT->value)]
#[Put(
path: self::PREFIX . '/user/{id}',
Expand Down
99 changes: 99 additions & 0 deletions src/User/Controller/UploadUserImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Controller;

use OpenApi\Attributes\Post;
use OpenApi\Attributes\Property;
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\DatabaseException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\EnvironmentException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ParseException;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request\MultipartFormDataRequestBody;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
use Pimcore\Bundle\StudioBackendBundle\User\Service\ImageUploadServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\UserPermissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\PaginatedResponseTrait;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\SerializerInterface;

/**
* @internal
*/
final class UploadUserImageController extends AbstractApiController
{
use PaginatedResponseTrait;

public function __construct(
SerializerInterface $serializer,
private readonly ImageUploadServiceInterface $imageUploadService
) {
parent::__construct($serializer);
}

/**
* @throws NotFoundException|DatabaseException|ForbiddenException|ParseException
*/
#[Route('/user/upload-image/{id}', name: 'pimcore_studio_api_user_upload_image', methods: ['POST'])]
#[IsGranted(UserPermissions::USER_MANAGEMENT->value)]
#[Post(
path: self::PREFIX . '/user/upload-image/{id}',
operationId: 'user_upload_image',
summary: 'user_upload_image_summary',
tags: [Tags::User->value]
)]
#[IdParameter(type: 'User')]
#[SuccessResponse]
#[MultipartFormDataRequestBody(
[
new Property(
property: 'userImage',
description: 'User image to upload',
type: 'string',
format: 'binary'
),
],
['userImage']
)]
#[DefaultResponses([
HttpResponseCodes::NOT_FOUND,
HttpResponseCodes::FORBIDDEN,
])]
public function uploadUserImage(
int $id,
// TODO: Symfony 7.1 change to https://symfony.com/blog/new-in-symfony-7-1-mapuploadedfile-attribute
Request $request
): Response {
$file = $request->files->get('userImage');
if (!$file instanceof UploadedFile) {
throw new EnvironmentException('Invalid file found in the request');
}

$this->imageUploadService->uploadUserImage($file, $id);

return new Response();
}
}
58 changes: 58 additions & 0 deletions src/User/Service/ImageUploadService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Service;

use Pimcore\Bundle\StaticResolverBundle\Models\Asset\AssetResolverInterface;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Repository\UserRepositoryInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
* @internal
*/
final readonly class ImageUploadService implements ImageUploadServiceInterface
{
public function __construct(
private UserRepositoryInterface $userRepository,
private SecurityServiceInterface $securityService,
private AssetResolverInterface $assetResolver
) {
}

/**
* @throws NotFoundException
*/
public function uploadUserImage(UploadedFile $file, int $userId): void
{
$user = $this->userRepository->getUserById($userId);
$currentUser = $this->securityService->getCurrentUser();

if ($user->isAdmin() && !$currentUser->isAdmin()) {
throw new ForbiddenException('You are not allowed to upload an image for an admin user');
}

$fileType = $this->assetResolver->getTypeFromMimeMapping($file->getMimeType(), $file->getFilename());

if ($fileType !== 'image') {
throw new ForbiddenException('Only images are allowed');
}

$user->setImage($file->getPathname());
}
}
27 changes: 27 additions & 0 deletions src/User/Service/ImageUploadServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Service;

use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
* @internal
*/
interface ImageUploadServiceInterface
{
public function uploadUserImage(UploadedFile $file, int $userId): void;
}
Loading

0 comments on commit 47dec90

Please sign in to comment.