diff --git a/README.md b/README.md index d12206a..0ba3b33 100644 --- a/README.md +++ b/README.md @@ -106,4 +106,7 @@ - File Uploading: https://api-platform.com/docs/core/file-upload/ https://github.com/dustin10/VichUploaderBundle/blob/master/docs/index.md ->>>>>>> Stashed changes + +# List Routes and Service Tags + - ddev exec bin/console debug:router + - ddev exec bin/console debug:container \ No newline at end of file diff --git a/composer.json b/composer.json index 841ff78..be19aa5 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,8 @@ "symfony/yaml": "7.0.*", "symfonycasts/micro-mapper": "^0.1.4", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/twig": "^2.12|^3.0", + "vich/uploader-bundle": "^2.3" }, "config": { "allow-plugins": { @@ -90,7 +91,7 @@ }, "extra": { "symfony": { - "allow-contrib": false, + "allow-contrib": true, "require": "7.0.*" } }, diff --git a/composer.lock b/composer.lock index afeb432..2b2d46b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90d8363fb071963db3a216753262c590", + "content-hash": "e41f83bf907500bc2cae3d1f8eb56816", "packages": [ { "name": "api-platform/core", @@ -1561,6 +1561,70 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "jms/metadata", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0|^2.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.8.0" + }, + "time": "2023-02-15T13:44:18+00:00" + }, { "name": "lcobucci/clock", "version": "3.2.0", @@ -7832,6 +7896,115 @@ ], "time": "2023-11-21T18:54:41+00:00" }, + { + "name": "vich/uploader-bundle", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/dustin10/VichUploaderBundle.git", + "reference": "0d748cfe676bcf894b3f324f69797c9b9d124aa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/0d748cfe676bcf894b3f324f69797c9b9d124aa5", + "reference": "0d748cfe676bcf894b3f324f69797c9b9d124aa5", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3", + "ext-simplexml": "*", + "jms/metadata": "^2.4", + "php": "^8.1", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher-contracts": "^3.1", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "symfony/property-access": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "league/flysystem": "<2.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.3", + "doctrine/doctrine-bundle": "^2.7", + "doctrine/mongodb-odm": "^2.4", + "doctrine/orm": "^2.13", + "ext-sqlite3": "*", + "knplabs/knp-gaufrette-bundle": "dev-master", + "league/flysystem-bundle": "^2.3", + "league/flysystem-memory": "^2.0", + "matthiasnoback/symfony-dependency-injection-test": "^4.3", + "mikey179/vfsstream": "^1.6.11", + "phpunit/phpunit": "^9.6", + "symfony/asset": "^5.4 || ^6.0", + "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^7.0", + "symfony/security-csrf": "^5.4 || ^6.0 || ^7.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "doctrine/annotations": "If you use doctrine/doctrine-bundle >2.7, this package is required to use annotations", + "doctrine/doctrine-bundle": "For integration with Doctrine", + "doctrine/mongodb-odm-bundle": "For integration with Doctrine ODM", + "doctrine/orm": "For integration with Doctrine ORM", + "doctrine/phpcr-odm": "For integration with Doctrine PHPCR", + "knplabs/knp-gaufrette-bundle": "For integration with Gaufrette", + "league/flysystem-bundle": "For integration with Flysystem", + "liip/imagine-bundle": "To generate image thumbnails", + "oneup/flysystem-bundle": "For integration with Flysystem", + "symfony/asset": "To generate better links", + "symfony/form": "To handle uploads in forms", + "symfony/yaml": "To use YAML mapping" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Vich\\UploaderBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dustin Dobervich", + "email": "ddobervich@gmail.com" + } + ], + "description": "Ease file uploads attached to entities", + "homepage": "https://github.com/dustin10/VichUploaderBundle", + "keywords": [ + "file uploads", + "upload" + ], + "support": { + "issues": "https://github.com/dustin10/VichUploaderBundle/issues", + "source": "https://github.com/dustin10/VichUploaderBundle/tree/2.3.0" + }, + "time": "2023-12-18T07:49:22+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/bundles.php b/config/bundles.php index 8ec8171..baebe09 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -17,4 +17,5 @@ return [ Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true], Symfonycasts\MicroMapper\SymfonycastsMicroMapperBundle::class => ['all' => true], Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], + Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], ]; diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml new file mode 100644 index 0000000..7bddc42 --- /dev/null +++ b/config/packages/vich_uploader.yaml @@ -0,0 +1,15 @@ +vich_uploader: + db_driver: orm + metadata: + type: attribute + mappings: + media_object: + uri_prefix: /media + upload_destination: '%kernel.project_dir%/public/media' + # Will rename uploaded files using a uniqueid as a prefix. + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer + download: + uri_prefix: /download + upload_destination: '%kernel.project_dir%/public/download' + # Will rename uploaded files using a uniqueid as a prefix. + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index aa7d877..ef0ae7b 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -27,4 +27,10 @@ services: class: App\EventListener\AuthenticationSuccessListener tags: - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse } - arguments: ['@symfonycasts.micro_mapper', '@api_platform.iri_converter'] \ No newline at end of file + arguments: ['@symfonycasts.micro_mapper', '@api_platform.iri_converter'] + + App\Serializer\MediaObjectNormalizer: + arguments: + $storage: '@vich_uploader.storage.file_system' + tags: + - { name: 'api_platform.jsonld.normalizer.item', priority: 1000 } \ No newline at end of file diff --git a/migrations/Version20240125102320.php b/migrations/Version20240125102320.php new file mode 100644 index 0000000..356bb44 --- /dev/null +++ b/migrations/Version20240125102320.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE media_object (id INT AUTO_INCREMENT NOT NULL, file_path VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE api_token DROP FOREIGN KEY FK_7BA2F5EB5E70BCD7'); + $this->addSql('DROP TABLE api_token'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE api_token (id INT AUTO_INCREMENT NOT NULL, owned_by_id INT NOT NULL, expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', token VARCHAR(68) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, scopes JSON NOT NULL COMMENT \'(DC2Type:json)\', INDEX IDX_7BA2F5EB5E70BCD7 (owned_by_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' '); + $this->addSql('ALTER TABLE api_token ADD CONSTRAINT FK_7BA2F5EB5E70BCD7 FOREIGN KEY (owned_by_id) REFERENCES user (id)'); + $this->addSql('DROP TABLE media_object'); + } +} diff --git a/src/Controller/CreateMediaObjectAction.php b/src/Controller/CreateMediaObjectAction.php new file mode 100644 index 0000000..819d16b --- /dev/null +++ b/src/Controller/CreateMediaObjectAction.php @@ -0,0 +1,33 @@ + + * @date 25.01.24 + */ + + +namespace App\Controller; + + +use App\Entity\MediaObject; +use App\Serializer\MediaObjectNormalizer; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; + +#[AsController] +final class CreateMediaObjectAction extends AbstractController +{ + public function __invoke(Request $request): MediaObject + { + $uploadedFile = $request->files->get('file'); + if (!$uploadedFile) { + throw new BadRequestHttpException('"file" is required'); + } + + $mediaObject = new MediaObject(); + $mediaObject->file = $uploadedFile; + + return $mediaObject; + } +} diff --git a/src/Entity/MediaObject.php b/src/Entity/MediaObject.php new file mode 100644 index 0000000..fed44a2 --- /dev/null +++ b/src/Entity/MediaObject.php @@ -0,0 +1,75 @@ + + * @date 25.01.24 + */ + + +namespace App\Entity; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model; +use App\Controller\CreateMediaObjectAction; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; +use Vich\UploaderBundle\Mapping\Annotation as Vich; + +#[Vich\Uploadable] +#[ORM\Entity] +#[ApiResource( + types: ['https://schema.org/MediaObject'], + operations: [ + new Get(), + new GetCollection(), + new Post( + controller: CreateMediaObjectAction::class, + openapi: new Model\Operation( + requestBody: new Model\RequestBody( + content: new \ArrayObject([ + 'multipart/form-data' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'file' => [ + 'type' => 'string', + 'format' => 'binary' + ] + ] + ] + ] + ]) + ) + ), + validationContext: ['groups' => ['Default', 'media_object_create']], + deserialize: false + ) + ], + normalizationContext: ['groups' => ['media_object:read']] +)] +class MediaObject +{ + #[ORM\Id, ORM\Column, ORM\GeneratedValue] + private ?int $id = null; + + #[ApiProperty(types: ['https://schema.org/contentUrl'])] + #[Groups(['media_object:read'])] + public ?string $contentUrl = null; + + #[Vich\UploadableField(mapping: 'media_object', fileNameProperty: 'filePath')] + #[Assert\NotNull(groups: ['media_object_create'])] + public ?File $file = null; + + #[ORM\Column(nullable: true)] + public ?string $filePath = null; + + public function getId(): ?int + { + return $this->id; + } +} \ No newline at end of file diff --git a/src/Serializer/MediaObjectNormalizer.php b/src/Serializer/MediaObjectNormalizer.php new file mode 100644 index 0000000..effc595 --- /dev/null +++ b/src/Serializer/MediaObjectNormalizer.php @@ -0,0 +1,63 @@ + + * @date 25.01.24 + */ + + +namespace App\Serializer; + + +use App\Entity\MediaObject; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Vich\UploaderBundle\Storage\StorageInterface; + +#[AsDecorator('api_platform.jsonld.normalizer.item')] +final class MediaObjectNormalizer implements NormalizerInterface, SerializerAwareInterface +{ + private const ALREADY_CALLED = 'MEDIA_OBJECT_NORMALIZER_ALREADY_CALLED'; + + public function __construct( + private StorageInterface $storage, + private NormalizerInterface $normalizer + ) + { + } + + public function normalize($object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + $context[self::ALREADY_CALLED] = true; + + $object->contentUrl = $this->storage->resolveUri($object, 'file'); + + return $this->normalizer->normalize($object, $format, $context); + } + + public function supportsNormalization($data, ?string $format = null, array $context = []): bool + { + if (isset($context[self::ALREADY_CALLED])) { + return false; + } + + return $data instanceof MediaObject; + } + + public function setSerializer(SerializerInterface $serializer): void + { + if ($this->normalizer instanceof SerializerAwareInterface) { + $this->normalizer->setSerializer($serializer); + } + } + + public function getSupportedTypes(?string $format): array + { + if (method_exists($this->normalizer, 'getSupportedTypes')) { + return $this->normalizer->getSupportedTypes($format); + } + + return 'jsonld' === $format ? ['*' => true] : []; + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index b0df86e..da1403c 100644 --- a/symfony.lock +++ b/symfony.lock @@ -300,6 +300,18 @@ "twig/extra-bundle": { "version": "v3.8.0" }, + "vich/uploader-bundle": { + "version": "2.3", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.13", + "ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36" + }, + "files": [ + "config/packages/vich_uploader.yaml" + ] + }, "zenstruck/foundry": { "version": "1.36", "recipe": {