From 090b37ab03683e7002ef4c1d451df8d982774c4f Mon Sep 17 00:00:00 2001 From: Kevin NGUYEN Date: Mon, 23 Oct 2023 07:09:15 +0200 Subject: [PATCH 1/2] Step 1 - Setup flysyste and minio implementation --- .env.dist | 16 +- apps/back/composer.json | 6 + apps/back/composer.lock | 1234 +++++++++++++++++++++- apps/back/config/bundles.php | 1 + apps/back/config/packages/flysystem.yaml | 43 + apps/back/config/services.yaml | 7 +- apps/back/src/Command/InitS3Buckets.php | 102 ++ apps/back/symfony.lock | 13 + docker-compose.override.yml.template | 1 + docker-compose.yml | 28 +- 10 files changed, 1432 insertions(+), 19 deletions(-) create mode 100644 apps/back/config/packages/flysystem.yaml create mode 100644 apps/back/src/Command/InitS3Buckets.php diff --git a/.env.dist b/.env.dist index 10f46604..e5773c89 100644 --- a/.env.dist +++ b/.env.dist @@ -9,6 +9,8 @@ BASE_DOMAIN="${APP_NAME}.localhost" API_SUBDOMAIN="api" API_ROUTE_FROM_BASE_DOMAIN="/api" PHPMYADMIN_DOMAIN="phpmyadmin.${BASE_DOMAIN}" +MINIO_CONSOLE_DOMAIN="minio-console.${BASE_DOMAIN}" +MINIO_DOMAIN="minio.${BASE_DOMAIN}" PROTOCOL="http" BACK_APP_NAME="back" @@ -16,6 +18,17 @@ FRONT_APP_NAME="front" API_DOMAIN="${API_SUBDOMAIN}.${BASE_DOMAIN}" +API_PREFIX="/api" + +# MINIO +STORAGE_S3_ENDPOINT="http://minio:9000" +STORAGE_S3_KEY="admin" +STORAGE_S3_SECRET="super-secret" +STORAGE_PRIVATE_SOURCE="private.storage.s3" +STORAGE_PUBLIC_SOURCE="public.storage.s3" +STORAGE_PUBLIC_BUCKET_NAME="public" +STORAGE_PRIVATE_BUCKET_NAME="private" + DEPLOYMENT_TAG=latest IMAGE_REGISTRY="git.thecodingmachine.com:444/tcm-projects/${APP_NAME}" @@ -25,6 +38,8 @@ FRONT_IMAGE_NAME="${IMAGE_REGISTRY}/${FRONT_APP_NAME}" BACK_ROUTER_RULE="Host(`${API_DOMAIN}`) || Host(`${BASE_DOMAIN}`) && PathPrefix(`${API_ROUTE_FROM_BASE_DOMAIN}`)" FRONT_ROUTER_RULE="Host(`${BASE_DOMAIN}`) && ! PathPrefix(`${API_ROUTE_FROM_BASE_DOMAIN}`)" PHPMYADMIN_ROUTER_RULE="Host(`${PHPMYADMIN_DOMAIN}`)" +MINIO_ROUTER_RULE="Host(`${MINIO_DOMAIN}`)" +MINIO_CONSOLE_ROUTER_RULE="Host(`${MINIO_CONSOLE_DOMAIN}`)" # SSO CONFIG APP_SSO_SERVICEPROVIDER_X509CERT="MIICkDCCAfmgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBlMQswCQYDVQQGEwJmcjEOMAwGA1UECAwFUEFSSVMxDDAKBgNVBAoMA1RDTTEaMBgGA1UEAwwRZnJvbnQuc29jb3RlYy5kZXYxDjAMBgNVBAcMBVBBUklTMQwwCgYDVQQLDANUQ00wHhcNMTcwOTA4MTUyNzM0WhcNMTgwOTA4MTUyNzM0WjBlMQswCQYDVQQGEwJmcjEOMAwGA1UECAwFUEFSSVMxDDAKBgNVBAoMA1RDTTEaMBgGA1UEAwwRZnJvbnQuc29jb3RlYy5kZXYxDjAMBgNVBAcMBVBBUklTMQwwCgYDVQQLDANUQ00wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMKCd0hnMKX40MYR+fZNRqMJjSiDpTPTkV9A0bfQKESZ9esPjNt8Janq+2MGLrm6cRcMXRx8yo/x7pfoCmdiu9D7VNhk69nFsNKH0PQp/jf2+vLPHXgKvlcCFvlaOB/Cvg9UnK9mq83H88LPwvrpaNRl4qDrLS5TTByEIohjFUJrAgMBAAGjUDBOMB0GA1UdDgQWBBSk8/zuzxKBEwpusxRAva7oY7MkrDAfBgNVHSMEGDAWgBSk8/zuzxKBEwpusxRAva7oY7MkrDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAGlGJPIGYAKKkhW/EAvJbprOAwLvSEansPR8iQlGOq49k/R+mvvTKyQ4DsUglrjbTqA90MZ3S7IG25rPtX6uG2Gmi8QdpJbvfbvkMqk4aNbHveWm35lhsFpiJhu5ngUvb7RjZAVg9YRJGMufZwA5TKrr57fIKhA45QXxbITEP2gX" @@ -38,4 +53,3 @@ APP_SSO_IDENTITYPROVIDER_LOGOUTURL="http://samltest.${BASE_DOMAIN}/simplesaml/sa ###> symfony/mailer ### MAILER_DSN=smtp://mail:1025 MAIL_HOST=admin@mail.com -API_PREFIX="/api" diff --git a/apps/back/composer.json b/apps/back/composer.json index 56dc0b19..2bff8185 100644 --- a/apps/back/composer.json +++ b/apps/back/composer.json @@ -11,6 +11,9 @@ "doctrine/doctrine-bundle": "^2.7", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.14", + "league/flysystem-aws-s3-v3": "^3.16", + "league/flysystem-bundle": "^3.2", + "league/flysystem-memory": "^3.16", "onelogin/php-saml": "^4.1", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.13", @@ -85,6 +88,9 @@ "databasemigrate": [ "Composer\\Config::disableProcessTimeout", "php bin/console -v --ansi" + ], + "lint-all": [ + "@phpstan", "@phpmd", "phpcbf --standard=phpcs.xml.dist || true", "@cs-check" ] }, "conflict": { diff --git a/apps/back/composer.lock b/apps/back/composer.lock index bbe5c9c3..da749bad 100644 --- a/apps/back/composer.lock +++ b/apps/back/composer.lock @@ -4,8 +4,157 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fe5b06caa25ffc5dfd8bcb1b292ec4c8", + "content-hash": "8d43b6ff41ff0d753e4fa9de6688a3b3", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + }, + "time": "2023-07-20T16:49:55+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.283.3", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "4cc8d6c7e856de80d9316f659c4c626d3713f6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4cc8d6c7e856de80d9316f659c4c626d3713f6c1", + "reference": "4cc8d6c7e856de80d9316f659c4c626d3713f6c1", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.4", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.283.3" + }, + "time": "2023-10-12T18:14:56+00:00" + }, { "name": "brick/math", "version": "0.11.0", @@ -1600,6 +1749,331 @@ ], "time": "2023-05-24T07:17:17+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-08-27T10:20:53+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-08-03T15:11:55+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-08-27T10:13:57+00:00" + }, { "name": "laminas/laminas-code", "version": "4.12.0", @@ -1648,20 +2122,411 @@ "laminasframework" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-code/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-code/issues", + "rss": "https://github.com/laminas/laminas-code/releases.atom", + "source": "https://github.com/laminas/laminas-code" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2023-09-06T14:56:25+00:00" + }, + { + "name": "league/flysystem", + "version": "3.17.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "bd4c9b26849d82364119c68429541f1631fba94b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd4c9b26849d82364119c68429541f1631fba94b", + "reference": "bd4c9b26849d82364119c68429541f1631fba94b", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.220.0", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "microsoft/azure-storage-blob": "^1.1", + "phpseclib/phpseclib": "^3.0.14", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.17.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-10-05T20:15:05+00:00" + }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "3.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c", + "reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.220.0", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.16.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-08-30T10:14:57+00:00" + }, + { + "name": "league/flysystem-bundle", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-bundle.git", + "reference": "c056bef0e8e0cdfb349e568d69e8337ce17ef6e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/c056bef0e8e0cdfb349e568d69e8337ce17ef6e1", + "reference": "c056bef0e8e0cdfb349e568d69e8337ce17ef6e1", + "shasum": "" + }, + "require": { + "league/flysystem": "^3.0", + "php": ">=8.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/options-resolver": "^5.4|^6.0" + }, + "require-dev": { + "league/flysystem-async-aws-s3": "^3.1", + "league/flysystem-aws-s3-v3": "^3.1", + "league/flysystem-azure-blob-storage": "^3.1", + "league/flysystem-ftp": "^3.1", + "league/flysystem-google-cloud-storage": "^3.1", + "league/flysystem-memory": "^3.1", + "league/flysystem-sftp-v3": "^3.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "League\\FlysystemBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + } + ], + "description": "Symfony bundle integrating Flysystem into Symfony 5.4+ applications", + "support": { + "issues": "https://github.com/thephpleague/flysystem-bundle/issues", + "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.2.0" + }, + "time": "2023-08-21T15:19:15+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ec7383f25642e6fd4bb0c9554fc2311245391781", + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-local/issues", + "source": "https://github.com/thephpleague/flysystem-local/tree/3.16.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-08-30T10:23:59+00:00" + }, + { + "name": "league/flysystem-memory", + "version": "3.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-memory.git", + "reference": "565a17ff0291c1432e0aefc999cca02501b8c0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/565a17ff0291c1432e0aefc999cca02501b8c0fc", + "reference": "565a17ff0291c1432e0aefc999cca02501b8c0fc", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\InMemory\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "In-memory filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "memory" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-memory/issues", + "source": "https://github.com/thephpleague/flysystem-memory/tree/3.16.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-08-30T10:23:38+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" } ], - "time": "2023-09-06T14:56:25+00:00" + "time": "2023-08-05T12:09:49+00:00" }, { "name": "monolog/monolog", @@ -1764,6 +2629,72 @@ ], "time": "2023-06-21T08:46:11+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + }, + "time": "2023-08-25T10:54:48+00:00" + }, { "name": "onelogin/php-saml", "version": "4.1.0", @@ -2235,6 +3166,166 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/log", "version": "3.0.0", @@ -2285,6 +3376,50 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "ramsey/collection", "version": "2.0.0", @@ -4355,6 +5490,73 @@ ], "time": "2022-05-10T14:24:36+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-12T14:21:09+00:00" + }, { "name": "symfony/password-hasher", "version": "v6.3.5", @@ -6813,16 +8015,16 @@ "packages-dev": [ { "name": "composer/pcre", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", "shasum": "" }, "require": { @@ -6864,7 +8066,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "source": "https://github.com/composer/pcre/tree/3.1.1" }, "funding": [ { @@ -6880,7 +8082,7 @@ "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2023-10-11T07:11:09+00:00" }, { "name": "composer/xdebug-handler", diff --git a/apps/back/config/bundles.php b/apps/back/config/bundles.php index 8504195c..9bbabcb5 100644 --- a/apps/back/config/bundles.php +++ b/apps/back/config/bundles.php @@ -10,4 +10,5 @@ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + League\FlysystemBundle\FlysystemBundle::class => ['all' => true], ]; diff --git a/apps/back/config/packages/flysystem.yaml b/apps/back/config/packages/flysystem.yaml new file mode 100644 index 00000000..2dff6b12 --- /dev/null +++ b/apps/back/config/packages/flysystem.yaml @@ -0,0 +1,43 @@ +# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md + +services: + Aws\S3\S3MultiRegionClient: + arguments: + - version: 'latest' + use_path_style_endpoint: true + endpoint: '%env(STORAGE_S3_ENDPOINT)%' + credentials: + key: '%env(STORAGE_S3_KEY)%' + secret: '%env(STORAGE_S3_SECRET)%' +flysystem: + storages: + # Private Storage + private.storage.s3: + adapter: 'aws' + options: + client: 'Aws\S3\S3MultiRegionClient' + bucket: '%env(STORAGE_PRIVATE_BUCKET_NAME)%' + + private.storage.memory: + adapter: 'memory' + + private.storage: + adapter: 'lazy' + options: + source: '%env(STORAGE_PRIVATE_SOURCE)%' + + public.storage.s3: + adapter: 'aws' + visibility: public + options: + client: 'Aws\S3\S3MultiRegionClient' + bucket: '%env(STORAGE_PUBLIC_BUCKET_NAME)%' + + public.storage.memory: + adapter: 'memory' + + public.storage: + adapter: 'lazy' + options: + source: '%env(STORAGE_PUBLIC_SOURCE)%' + diff --git a/apps/back/config/services.yaml b/apps/back/config/services.yaml index ee26e9f5..ef9621b9 100644 --- a/apps/back/config/services.yaml +++ b/apps/back/config/services.yaml @@ -75,4 +75,9 @@ services: tags: - {name: controller.service_arguments} arguments: - $appUrl: "%app.url.base%" \ No newline at end of file + $appUrl: "%app.url.base%" + + App\Command\InitS3Buckets: + arguments: + $publicBucketName: "%env(STORAGE_PUBLIC_BUCKET_NAME)%" + $privateBucketName: "%env(STORAGE_PRIVATE_BUCKET_NAME)%" diff --git a/apps/back/src/Command/InitS3Buckets.php b/apps/back/src/Command/InitS3Buckets.php new file mode 100644 index 00000000..77eaa59a --- /dev/null +++ b/apps/back/src/Command/InitS3Buckets.php @@ -0,0 +1,102 @@ +info('Initializing buckets...'); + $this->createBucket($this->privateBucketName, $style); + + if ($this->createBucket($this->publicBucketName, $style)) { + //Add DL rights for public bucket + $policyReadOnly = '{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:GetBucketLocation", + "s3:ListBucket" + ], + "Effect": "Allow", + "Principal": { + "AWS": [ + "*" + ] + }, + "Resource": [ + "arn:aws:s3:::%s" + ], + "Sid": "" + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Principal": { + "AWS": [ + "*" + ] + }, + "Resource": [ + "arn:aws:s3:::%s/*" + ], + "Sid": "" + } + ] + } + '; + + $this->client->putBucketPolicy([ + 'Bucket' => $this->publicBucketName, + 'Policy' => sprintf( + $policyReadOnly, + $this->publicBucketName, + $this->publicBucketName, + ), + ]); + } + + $style->success('Buckets init done !'); + + return Command::SUCCESS; + } + + public function createBucket(string $bucketName, SymfonyStyle $style): bool + { + $buckets = $this->client->listBuckets(); + if (is_iterable($buckets->get('Buckets'))) { + foreach ($buckets->get('Buckets') as $bucket) { + if ($bucket['Name'] === $bucketName) { + $style->info(sprintf("Bucket '%s' exists, skipping...", $bucketName)); + + return false; + } + } + } + + $this->client->createBucket(['Bucket' => $bucketName]); + $style->success(sprintf("Bucket '%s' created !", $bucketName)); + + return true; + } +} diff --git a/apps/back/symfony.lock b/apps/back/symfony.lock index c047dba9..316467ea 100644 --- a/apps/back/symfony.lock +++ b/apps/back/symfony.lock @@ -56,6 +56,19 @@ "migrations/.gitignore" ] }, + "league/flysystem-bundle": { + "version": "3.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "913dc3d7a5a1af0d2b044c5ac3a16e2f851d7380" + }, + "files": [ + "config/packages/flysystem.yaml", + "var/storage/.gitignore" + ] + }, "phpstan/phpstan": { "version": "1.10", "recipe": { diff --git a/docker-compose.override.yml.template b/docker-compose.override.yml.template index 8fae87ad..78057e46 100644 --- a/docker-compose.override.yml.template +++ b/docker-compose.override.yml.template @@ -39,6 +39,7 @@ services: - | composer install composer run console -- doctrine:migrations:migrate -n + composer run console -- app:storage:init-buckets phpmyadmin: image: phpmyadmin diff --git a/docker-compose.yml b/docker-compose.yml index b18aaefa..85af19d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,13 @@ services: MAILER_DSN: ${MAILER_DSN} MAIL_HOST: ${MAIL_HOST} APP_SECRET: "secretforapp" + STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} + STORAGE_S3_KEY: ${STORAGE_S3_KEY} + STORAGE_S3_SECRET: ${STORAGE_S3_SECRET} + STORAGE_PUBLIC_SOURCE: ${STORAGE_PUBLIC_SOURCE} + STORAGE_PRIVATE_SOURCE: ${STORAGE_PRIVATE_SOURCE} + STORAGE_PUBLIC_BUCKET_NAME: ${STORAGE_PUBLIC_BUCKET_NAME} + STORAGE_PRIVATE_BUCKET_NAME: ${STORAGE_PRIVATE_BUCKET_NAME} # APP_URL_WEBAPP: "${PROTOCOL}://${DOMAIN}" mysql: @@ -47,7 +54,8 @@ services: working_dir: /home/node/app environment: # This is used only per the proxy - NUXT_API_URL: http://${API_DOMAIN}/ + NUXT_API_URL: "${PROTOCOL}://${API_DOMAIN}/" + PUBLIC_STORAGE_URL: "${PROTOCOL}://${MINIO_DOMAIN}/${STORAGE_PUBLIC_BUCKET_NAME}/" proxy: image: traefik:3.0 container_name: tcm_proxy @@ -67,8 +75,26 @@ services: - "${API_DOMAIN}" - "${BASE_DOMAIN}" + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + labels: + - traefik.enable=true + - traefik.http.routers.minio_console_router.rule=${MINIO_CONSOLE_ROUTER_RULE} + - traefik.http.routers.minio_console_router.service=minio_console_service + - traefik.http.services.minio_console_service.loadbalancer.server.port=9001 + - traefik.http.routers.minio_s3_router.rule=${MINIO_ROUTER_RULE} + - traefik.http.routers.minio_s3_router.service=minio_s3_service + - traefik.http.services.minio_s3_service.loadbalancer.server.port=9000 + environment: + MINIO_ROOT_USER: ${STORAGE_S3_KEY} + MINIO_ROOT_PASSWORD: ${STORAGE_S3_SECRET} + volumes: + - minio_data:/data + volumes: mysql_db: + minio_data: networks: # This could be very usefull to run multiple project on the same machine tcm_network: From d7683ef458baacf008f5e658ba16b6cef95f9db1 Mon Sep 17 00:00:00 2001 From: Kevin NGUYEN Date: Mon, 23 Oct 2023 07:11:00 +0200 Subject: [PATCH 2/2] Step 2 - Implement public and private file uploads, starabe, and File entity --- Makefile | 4 + apps/back/config/packages/security.yaml | 4 + .../back/migrations/Version20231005121328.php | 37 +++++++ .../back/migrations/Version20231006041542.php | 33 +++++++ .../back/migrations/Version20231015065234.php | 43 ++++++++ .../back/migrations/Version20231022121013.php | 31 ++++++ .../back/src/Controller/CompanyController.php | 75 ++++++++++++++ apps/back/src/Controller/UserController.php | 24 ++++- .../Dto/Request/Company/CompanyRequestDto.php | 32 ++++++ .../Dto/Request/{ => User}/CreateUserDto.php | 4 +- .../src/Dto/Request/User/ProfilePicture.php | 22 +++++ .../Dto/Request/{ => User}/UpdateUserDto.php | 4 +- apps/back/src/Entity/Company.php | 98 +++++++++++++++++++ apps/back/src/Entity/File.php | 67 +++++++++++++ apps/back/src/Entity/User.php | 37 +++++-- .../back/src/Repository/CompanyRepository.php | 24 +++++ apps/back/src/Repository/FileRepository.php | 24 +++++ .../src/UseCase/Company/CreateCompany.php | 27 +++++ .../src/UseCase/Company/UpdateCompany.php | 27 +++++ apps/back/src/UseCase/Storage/StoreFile.php | 79 +++++++++++++++ apps/back/src/UseCase/User/CreateUser.php | 8 +- apps/back/src/UseCase/User/UpdateUser.php | 9 +- apps/back/tests/UseCase/CreateUserTest.php | 2 +- .../src/components/company/CompanyForm.vue | 39 ++++++++ .../src/components/layout/menu/AppMenu.vue | 5 + .../src/components/user/UserCreateForm.vue | 9 +- apps/front/src/components/user/UserForm.vue | 15 ++- .../src/components/user/UserUpdateForm.vue | 7 +- .../api/company/useDeleteCompany.ts | 17 ++++ .../composables/api/company/useGetCompany.ts | 10 ++ .../api/company/useListCompanies.ts | 10 ++ .../composables/api/company/useSaveCompany.ts | 28 ++++++ .../src/composables/api/user/useCreateUser.ts | 13 +-- .../src/composables/api/user/useUpdateUser.ts | 23 +++-- apps/front/src/composables/user/useUser.ts | 4 + apps/front/src/pages/companies/[id].vue | 31 ++++++ apps/front/src/pages/companies/create.vue | 25 +++++ apps/front/src/pages/companies/index.vue | 65 ++++++++++++ apps/front/src/types/Company.d.ts | 9 ++ apps/front/src/types/User.d.ts | 1 + 40 files changed, 988 insertions(+), 38 deletions(-) create mode 100644 apps/back/migrations/Version20231005121328.php create mode 100644 apps/back/migrations/Version20231006041542.php create mode 100644 apps/back/migrations/Version20231015065234.php create mode 100644 apps/back/migrations/Version20231022121013.php create mode 100644 apps/back/src/Controller/CompanyController.php create mode 100644 apps/back/src/Dto/Request/Company/CompanyRequestDto.php rename apps/back/src/Dto/Request/{ => User}/CreateUserDto.php (90%) create mode 100644 apps/back/src/Dto/Request/User/ProfilePicture.php rename apps/back/src/Dto/Request/{ => User}/UpdateUserDto.php (92%) create mode 100644 apps/back/src/Entity/Company.php create mode 100644 apps/back/src/Entity/File.php create mode 100644 apps/back/src/Repository/CompanyRepository.php create mode 100644 apps/back/src/Repository/FileRepository.php create mode 100644 apps/back/src/UseCase/Company/CreateCompany.php create mode 100644 apps/back/src/UseCase/Company/UpdateCompany.php create mode 100644 apps/back/src/UseCase/Storage/StoreFile.php create mode 100644 apps/front/src/components/company/CompanyForm.vue create mode 100644 apps/front/src/composables/api/company/useDeleteCompany.ts create mode 100644 apps/front/src/composables/api/company/useGetCompany.ts create mode 100644 apps/front/src/composables/api/company/useListCompanies.ts create mode 100644 apps/front/src/composables/api/company/useSaveCompany.ts create mode 100644 apps/front/src/pages/companies/[id].vue create mode 100644 apps/front/src/pages/companies/create.vue create mode 100644 apps/front/src/pages/companies/index.vue create mode 100644 apps/front/src/types/Company.d.ts diff --git a/Makefile b/Makefile index ba44577b..d1c8af11 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,10 @@ cs-check: sync-env ## cs-check phpstan: sync-env ## phpstan docker compose exec back composer -- run phpstan +.PHONY: backlint +backlint: sync-env ## phpstan + docker compose exec back composer -- run lint-all + .PHONY: frontlint frontlint: sync-env ## lint front (fix) docker compose exec front yarn lint --fix diff --git a/apps/back/config/packages/security.yaml b/apps/back/config/packages/security.yaml index 4f64408f..d0a54809 100644 --- a/apps/back/config/packages/security.yaml +++ b/apps/back/config/packages/security.yaml @@ -45,6 +45,10 @@ security: - ROLE_RIGHT_USER_CREATE - ROLE_RIGHT_USER_DELETE - ROLE_RIGHT_USER_UPDATE + - ROLE_RIGHT_COMPANY_READ + - ROLE_RIGHT_COMPANY_CREATE + - ROLE_RIGHT_COMPANY_DELETE + - ROLE_RIGHT_COMPANY_UPDATE ROLE_USER: - ROLE_RIGHT_ACCESS diff --git a/apps/back/migrations/Version20231005121328.php b/apps/back/migrations/Version20231005121328.php new file mode 100644 index 00000000..28435165 --- /dev/null +++ b/apps/back/migrations/Version20231005121328.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE company (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE user ADD company_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D649979B1AD6 FOREIGN KEY (company_id) REFERENCES company (id)'); + $this->addSql('CREATE INDEX IDX_8D93D649979B1AD6 ON user (company_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP FOREIGN KEY FK_8D93D649979B1AD6'); + $this->addSql('DROP TABLE company'); + $this->addSql('DROP INDEX IDX_8D93D649979B1AD6 ON user'); + $this->addSql('ALTER TABLE user DROP company_id'); + } +} diff --git a/apps/back/migrations/Version20231006041542.php b/apps/back/migrations/Version20231006041542.php new file mode 100644 index 00000000..e2794099 --- /dev/null +++ b/apps/back/migrations/Version20231006041542.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE company ADD identity_file VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE user ADD profile_picture VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE company DROP identity_file'); + $this->addSql('ALTER TABLE user DROP profile_picture'); + } +} diff --git a/apps/back/migrations/Version20231015065234.php b/apps/back/migrations/Version20231015065234.php new file mode 100644 index 00000000..6928d786 --- /dev/null +++ b/apps/back/migrations/Version20231015065234.php @@ -0,0 +1,43 @@ +addSql('CREATE TABLE file (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, private TINYINT(1) NOT NULL, storage_path VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE company ADD indentity_file_id INT NOT NULL'); + $this->addSql('ALTER TABLE company ADD CONSTRAINT FK_4FBF094FDB804B4A FOREIGN KEY (indentity_file_id) REFERENCES file (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_4FBF094FDB804B4A ON company (indentity_file_id)'); + $this->addSql('ALTER TABLE user ADD profile_picture_id INT DEFAULT NULL, DROP profile_picture, CHANGE roles roles JSON NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D649292E8AE2 FOREIGN KEY (profile_picture_id) REFERENCES file (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649292E8AE2 ON user (profile_picture_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE company DROP FOREIGN KEY FK_4FBF094FDB804B4A'); + $this->addSql('ALTER TABLE user DROP FOREIGN KEY FK_8D93D649292E8AE2'); + $this->addSql('DROP TABLE file'); + $this->addSql('DROP INDEX UNIQ_4FBF094FDB804B4A ON company'); + $this->addSql('ALTER TABLE company ADD identity_file VARCHAR(255) DEFAULT NULL, DROP indentity_file_id'); + $this->addSql('DROP INDEX UNIQ_8D93D649292E8AE2 ON user'); + $this->addSql('ALTER TABLE user ADD profile_picture VARCHAR(255) DEFAULT NULL, DROP profile_picture_id, CHANGE roles roles JSON NOT NULL COMMENT \'(DC2Type:json)\''); + } +} diff --git a/apps/back/migrations/Version20231022121013.php b/apps/back/migrations/Version20231022121013.php new file mode 100644 index 00000000..b322847c --- /dev/null +++ b/apps/back/migrations/Version20231022121013.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE company CHANGE indentity_file_id indentity_file_id INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE company CHANGE indentity_file_id indentity_file_id INT NOT NULL'); + } +} diff --git a/apps/back/src/Controller/CompanyController.php b/apps/back/src/Controller/CompanyController.php new file mode 100644 index 00000000..ed45a382 --- /dev/null +++ b/apps/back/src/Controller/CompanyController.php @@ -0,0 +1,75 @@ +companyRepository->findAll(); + + return $this->json($companies, 200, [], ['groups' => Company::GROUP_LIST]); + } + + #[Route('/companies/{id}', name: 'get_company', methods: ['GET'])] + #[IsGranted('ROLE_RIGHT_COMPANY_READ')] + public function get(Company $company): JsonResponse + { + return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]); + } + + #[Route('/companies', name: 'create_company', methods: ['POST'])] + #[IsGranted('ROLE_RIGHT_COMPANY_CREATE')] + public function create(#[MapRequestPayload] CompanyRequestDto $companyRequestDto, Request $request): JsonResponse + { + $identityFile = $request->files->get('identityFile'); + if ($identityFile !== null) { + \assert($identityFile instanceof UploadedFile); + $companyRequestDto->setIndentityFile($identityFile); + } + $company = $this->createCompany->create($companyRequestDto); + $this->entityManager->flush(); + + return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]); + } + + #[Route('/companies/{id}', name: 'update_company', methods: ['POST'])] + #[IsGranted('ROLE_RIGHT_COMPANY_UPDATE')] + public function update(Company $company, #[MapRequestPayload] CompanyRequestDto $companyRequestDto, Request $request): JsonResponse + { + $identityFile = $request->files->get('identityFile'); + if ($identityFile !== null) { + \assert($identityFile instanceof UploadedFile); + $companyRequestDto->setIndentityFile($identityFile); + } + $company = $this->updateCompany->update($company, $companyRequestDto); + $this->entityManager->flush(); + + return $this->json($company, 200, [], ['groups' => Company::GROUP_DETAILS]); + } +} diff --git a/apps/back/src/Controller/UserController.php b/apps/back/src/Controller/UserController.php index cae13992..0afb9693 100644 --- a/apps/back/src/Controller/UserController.php +++ b/apps/back/src/Controller/UserController.php @@ -4,14 +4,16 @@ namespace App\Controller; -use App\Dto\Request\CreateUserDto; -use App\Dto\Request\UpdateUserDto; +use App\Dto\Request\User\CreateUserDto; +use App\Dto\Request\User\UpdateUserDto; use App\Entity\User; use App\Repository\UserRepository; use App\UseCase\User\CreateUser; use App\UseCase\User\UpdateUser; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -27,8 +29,13 @@ public function __construct( } #[Route('/users', name: 'create_user', methods: ['POST'])] - public function createUser(#[MapRequestPayload] CreateUserDto $userDto): JsonResponse + public function createUser(#[MapRequestPayload] CreateUserDto $userDto, Request $request): JsonResponse { + $profilePicture = $request->files->get('profilePictureFile'); + if ($profilePicture !== null) { + \assert($profilePicture instanceof UploadedFile); + $userDto->setProfilePicture($profilePicture); + } $user = $this->createUser->createUser($userDto); $this->entityManager->flush(); @@ -54,10 +61,17 @@ public function getUser(User $user): JsonResponse ]); } - #[Route('/users/{id}', name: 'update_user', methods: ['PUT'])] + #[Route('/users/{id}', name: 'update_user', methods: ['POST'])] #[IsGranted('ROLE_RIGHT_USER_UPDATE')] - public function updateUser(User $user, #[MapRequestPayload] UpdateUserDto $userDto): JsonResponse + public function updateUser(User $user, #[MapRequestPayload] UpdateUserDto $userDto, Request $request): JsonResponse { + // phpcs:disable Generic.Commenting.Fixme.TaskFound + // FIXME: Using POST instead of PUT here because PUT does not handle multipart form data correctly + $profilePicture = $request->files->get('profilePictureFile'); + if ($profilePicture !== null) { + \assert($profilePicture instanceof UploadedFile); + $userDto->setProfilePicture($profilePicture); + } $user = $this->updateUser->updateUser($user, $userDto); $this->entityManager->flush(); diff --git a/apps/back/src/Dto/Request/Company/CompanyRequestDto.php b/apps/back/src/Dto/Request/Company/CompanyRequestDto.php new file mode 100644 index 00000000..21652905 --- /dev/null +++ b/apps/back/src/Dto/Request/Company/CompanyRequestDto.php @@ -0,0 +1,32 @@ +name; + } + + public function getIndentityFile(): UploadedFile|null + { + return $this->indentityFile; + } + + public function setIndentityFile(UploadedFile|null $indentityFile): void + { + $this->indentityFile = $indentityFile; + } +} diff --git a/apps/back/src/Dto/Request/CreateUserDto.php b/apps/back/src/Dto/Request/User/CreateUserDto.php similarity index 90% rename from apps/back/src/Dto/Request/CreateUserDto.php rename to apps/back/src/Dto/Request/User/CreateUserDto.php index 7f8bcf7e..53b7b79f 100644 --- a/apps/back/src/Dto/Request/CreateUserDto.php +++ b/apps/back/src/Dto/Request/User/CreateUserDto.php @@ -2,12 +2,14 @@ declare(strict_types=1); -namespace App\Dto\Request; +namespace App\Dto\Request\User; use Symfony\Component\Validator\Constraints as Assert; class CreateUserDto { + use ProfilePicture; + public function __construct( #[Assert\Email] #[Assert\NotBlank] diff --git a/apps/back/src/Dto/Request/User/ProfilePicture.php b/apps/back/src/Dto/Request/User/ProfilePicture.php new file mode 100644 index 00000000..5cdbcdd2 --- /dev/null +++ b/apps/back/src/Dto/Request/User/ProfilePicture.php @@ -0,0 +1,22 @@ +profilePicture; + } + + public function setProfilePicture(UploadedFile|null $profilePicture): void + { + $this->profilePicture = $profilePicture; + } +} diff --git a/apps/back/src/Dto/Request/UpdateUserDto.php b/apps/back/src/Dto/Request/User/UpdateUserDto.php similarity index 92% rename from apps/back/src/Dto/Request/UpdateUserDto.php rename to apps/back/src/Dto/Request/User/UpdateUserDto.php index e3ea0999..3701396d 100644 --- a/apps/back/src/Dto/Request/UpdateUserDto.php +++ b/apps/back/src/Dto/Request/User/UpdateUserDto.php @@ -2,12 +2,14 @@ declare(strict_types=1); -namespace App\Dto\Request; +namespace App\Dto\Request\User; use Symfony\Component\Validator\Constraints as Assert; class UpdateUserDto { + use ProfilePicture; + public function __construct( #[Assert\Email] private string $email, diff --git a/apps/back/src/Entity/Company.php b/apps/back/src/Entity/Company.php new file mode 100644 index 00000000..de155561 --- /dev/null +++ b/apps/back/src/Entity/Company.php @@ -0,0 +1,98 @@ + */ + #[ORM\OneToMany(mappedBy: 'company', targetEntity: User::class)] + private Collection $users; + + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + #[ORM\JoinColumn] + private File|null $indentityFile = null; + + public function __construct() + { + $this->users = new ArrayCollection(); + } + + #[Groups([self::GROUP_LIST, self::GROUP_DETAILS])] + public function getId(): int|null + { + return $this->id; + } + + #[Groups([self::GROUP_LIST, self::GROUP_DETAILS])] + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + /** @return Collection */ + public function getUsers(): Collection + { + return $this->users; + } + + public function addUser(User $user): static + { + if (!$this->users->contains($user)) { + $this->users->add($user); + $user->setCompany($this); + } + + return $this; + } + + public function removeUser(User $user): static + { + if ($this->users->removeElement($user)) { + // set the owning side to null (unless already changed) + if ($user->getCompany() === $this) { + $user->setCompany(null); + } + } + + return $this; + } + + public function getIndentityFile(): File|null + { + return $this->indentityFile; + } + + public function setIndentityFile(File $indentityFile): static + { + $this->indentityFile = $indentityFile; + + return $this; + } +} diff --git a/apps/back/src/Entity/File.php b/apps/back/src/Entity/File.php new file mode 100644 index 00000000..b92211ea --- /dev/null +++ b/apps/back/src/Entity/File.php @@ -0,0 +1,67 @@ +id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function isPrivate(): bool + { + return $this->private; + } + + public function setPrivate(bool $private): static + { + $this->private = $private; + + return $this; + } + + public function getStoragePath(): string + { + return $this->storagePath; + } + + public function setStoragePath(string $storagePath): static + { + $this->storagePath = $storagePath; + + return $this; + } +} diff --git a/apps/back/src/Entity/User.php b/apps/back/src/Entity/User.php index a8632c40..4e1d7286 100644 --- a/apps/back/src/Entity/User.php +++ b/apps/back/src/Entity/User.php @@ -20,6 +20,12 @@ class User implements UserInterface, \JsonSerializable, PasswordAuthenticatedUse #[ORM\Column(length: 180)] private string $password; + #[ORM\ManyToOne(inversedBy: 'users')] + private Company|null $company = null; + + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private File|null $profilePicture = null; + /** @param array $roles */ public function __construct( #[ORM\Column(length: 180, unique: true)] @@ -44,11 +50,6 @@ public function setEmail(string $email): void $this->email = $email; } - /** - * A visual identifier that represents this user. - * - * @see UserInterface - */ public function getUserIdentifier(): string { return $this->email; @@ -116,12 +117,34 @@ public function eraseCredentials(): void // $this->plainPassword = null; } - public function jsonSerialize(): mixed + /** @return array */ + public function jsonSerialize(): array { return [ 'id' => $this->getId(), 'email' => $this->getEmail(), - 'username' => $this->getUsername(), ]; } + + public function getCompany(): Company|null + { + return $this->company; + } + + public function setCompany(Company|null $company): static + { + $this->company = $company; + + return $this; + } + + public function getProfilePicture(): File|null + { + return $this->profilePicture; + } + + public function setProfilePicture(File|null $profilePicture): void + { + $this->profilePicture = $profilePicture; + } } diff --git a/apps/back/src/Repository/CompanyRepository.php b/apps/back/src/Repository/CompanyRepository.php new file mode 100644 index 00000000..502c3ae4 --- /dev/null +++ b/apps/back/src/Repository/CompanyRepository.php @@ -0,0 +1,24 @@ + + * @method Company|null find($id, $lockMode = null, $lockVersion = null) + * @method Company|null findOneBy(array $criteria, array $orderBy = null) + * @method array findAll() + * @method array findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class CompanyRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Company::class); + } +} diff --git a/apps/back/src/Repository/FileRepository.php b/apps/back/src/Repository/FileRepository.php new file mode 100644 index 00000000..6fe235ff --- /dev/null +++ b/apps/back/src/Repository/FileRepository.php @@ -0,0 +1,24 @@ + + * @method File|null find($id, $lockMode = null, $lockVersion = null) + * @method File|null findOneBy(array $criteria, array $orderBy = null) + * @method array findAll() + * @method array findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class FileRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, File::class); + } +} diff --git a/apps/back/src/UseCase/Company/CreateCompany.php b/apps/back/src/UseCase/Company/CreateCompany.php new file mode 100644 index 00000000..ae415c6b --- /dev/null +++ b/apps/back/src/UseCase/Company/CreateCompany.php @@ -0,0 +1,27 @@ +updateCompany->update($company, $companyRequestDto); + $this->entityManager->persist($company); + + return $company; + } +} diff --git a/apps/back/src/UseCase/Company/UpdateCompany.php b/apps/back/src/UseCase/Company/UpdateCompany.php new file mode 100644 index 00000000..bfece823 --- /dev/null +++ b/apps/back/src/UseCase/Company/UpdateCompany.php @@ -0,0 +1,27 @@ +setName($companyRequestDto->getName()); + if ($companyRequestDto->getIndentityFile() !== null) { + $identityFile = $this->storeFile->storeUploadedCompanyIdentityFile($companyRequestDto->getIndentityFile(), $company); + $company->setIndentityFile($identityFile); + } + + return $company; + } +} diff --git a/apps/back/src/UseCase/Storage/StoreFile.php b/apps/back/src/UseCase/Storage/StoreFile.php new file mode 100644 index 00000000..14a2e33a --- /dev/null +++ b/apps/back/src/UseCase/Storage/StoreFile.php @@ -0,0 +1,79 @@ +storeUploadedFile( + $uploadedFile, + false, + $user->getProfilePicture(), + self::STORAGE_PREFIX_USER_PROFILE_PICTURES, + ); + } + + public function storeUploadedCompanyIdentityFile( + UploadedFile $uploadedFile, + Company $company, + ): File { + return $this->storeUploadedFile( + $uploadedFile, + true, + $company->getIndentityFile(), + self::STORAGE_PREFIX_COMPANY_IDENTITY_FILES, + ); + } + + private function storeUploadedFile( + UploadedFile $uploadedFile, + bool $isPrivate, + File|null $replaceFile = null, + string|null $storagePrefix = null, + ): File { + $storage = $isPrivate ? $this->privateStorage : $this->publicStorage; + $fileName = Uuid::uuid4()->toString() . '.' . $uploadedFile->getClientOriginalExtension(); + $filePath = ($storagePrefix ? $storagePrefix . '/' : '') . $fileName; + + $file = new File(); + if ($replaceFile !== null) { + $file = $replaceFile; + if ($storage->fileExists($replaceFile->getStoragePath())) { + $storage->delete($replaceFile->getStoragePath()); + } + } + + $file + ->setName($uploadedFile->getClientOriginalName()) + ->setPrivate($isPrivate) + ->setStoragePath($filePath); + + $this->entityManager->persist($file); + $storage->write($filePath, $uploadedFile->getContent()); + + return $file; + } +} diff --git a/apps/back/src/UseCase/User/CreateUser.php b/apps/back/src/UseCase/User/CreateUser.php index 7d54c392..236d51f8 100644 --- a/apps/back/src/UseCase/User/CreateUser.php +++ b/apps/back/src/UseCase/User/CreateUser.php @@ -4,8 +4,8 @@ namespace App\UseCase\User; -use App\Dto\Request\CreateUserDto; -use App\Dto\Request\UpdateUserDto; +use App\Dto\Request\User\CreateUserDto; +use App\Dto\Request\User\UpdateUserDto; use App\Entity\User; use App\Mailer\UserMailer; use App\Repository\UserRepository; @@ -29,7 +29,9 @@ public function createUser(CreateUserDto $userDto): User } $user = new User($userDto->getEmail()); - $this->updateUser->updateUser($user, new UpdateUserDto($userDto->getEmail(), $userDto->getPassword())); + $userUpdateDto = new UpdateUserDto($userDto->getEmail(), $userDto->getPassword()); + $userUpdateDto->setProfilePicture($userDto->getProfilePicture()); + $this->updateUser->updateUser($user, $userUpdateDto); $this->entityManager->persist($user); $this->userMailer->sendRegistrationMail($user); diff --git a/apps/back/src/UseCase/User/UpdateUser.php b/apps/back/src/UseCase/User/UpdateUser.php index 9e0913df..2e787cec 100644 --- a/apps/back/src/UseCase/User/UpdateUser.php +++ b/apps/back/src/UseCase/User/UpdateUser.php @@ -4,14 +4,16 @@ namespace App\UseCase\User; -use App\Dto\Request\UpdateUserDto; +use App\Dto\Request\User\UpdateUserDto; use App\Entity\User; +use App\UseCase\Storage\StoreFile; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class UpdateUser { public function __construct( private readonly UserPasswordHasherInterface $passwordHasher, + private readonly StoreFile $storeFile, ) { } @@ -23,6 +25,11 @@ public function updateUser(User $user, UpdateUserDto $userDto): User $user->setPassword($this->passwordHasher->hashPassword($user, $userDto->getPassword())); } + if ($userDto->getProfilePicture() !== null) { + $pictureFile = $this->storeFile->storeUploadedUserPicture($userDto->getProfilePicture(), $user); + $user->setProfilePicture($pictureFile); + } + return $user; } } diff --git a/apps/back/tests/UseCase/CreateUserTest.php b/apps/back/tests/UseCase/CreateUserTest.php index 460e546f..9005464c 100644 --- a/apps/back/tests/UseCase/CreateUserTest.php +++ b/apps/back/tests/UseCase/CreateUserTest.php @@ -2,7 +2,7 @@ namespace UseCase; -use App\Dto\Request\CreateUserDto; +use App\Dto\Request\User\CreateUserDto; use App\Entity\User; use App\UseCase\User\CreateUser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; diff --git a/apps/front/src/components/company/CompanyForm.vue b/apps/front/src/components/company/CompanyForm.vue new file mode 100644 index 00000000..250fca93 --- /dev/null +++ b/apps/front/src/components/company/CompanyForm.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/apps/front/src/components/layout/menu/AppMenu.vue b/apps/front/src/components/layout/menu/AppMenu.vue index 55aa03a9..65681e0c 100644 --- a/apps/front/src/components/layout/menu/AppMenu.vue +++ b/apps/front/src/components/layout/menu/AppMenu.vue @@ -13,6 +13,11 @@ const items = computed(() => [ icon: "pi pi-fw pi-file", to: "/users", }, + { + label: t("components.layout.menu.appMenu.companies"), + icon: "pi pi-fw pi-file", + to: "/companies", + }, { label: t("components.layout.menu.appMenu.page1"), icon: "pi pi-fw pi-pencil", diff --git a/apps/front/src/components/user/UserCreateForm.vue b/apps/front/src/components/user/UserCreateForm.vue index 17a16188..effd5ab8 100644 --- a/apps/front/src/components/user/UserCreateForm.vue +++ b/apps/front/src/components/user/UserCreateForm.vue @@ -6,6 +6,8 @@ v-model:password="password" v-model:password-confirm="passwordConfirm" :is-password-confirmed="isPasswordConfirmed" + v-model:profile-picture-file="profilePictureFile" + :profile-picture-url="profilePictureUrl" /> {{ errorMessage }}