From d4cc71946d45c55b6d79ecd6620cc96f4635af0b Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 22 May 2024 17:16:38 +0200 Subject: [PATCH 01/29] Add species fixture --- composer.json | 2 + composer.lock | 236 ++++++++++++++++++++++++++++++- config/bundles.php | 1 + src/DataFixtures/AppFixtures.php | 34 +++++ src/Entity/Post.php | 15 ++ src/Entity/Species.php | 43 ++++++ symfony.lock | 12 ++ 7 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 src/DataFixtures/AppFixtures.php diff --git a/composer.json b/composer.json index a582dc8..39e0ac0 100644 --- a/composer.json +++ b/composer.json @@ -95,6 +95,8 @@ } }, "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.6", + "fakerphp/faker": "^1.23", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "7.0.*", "symfony/css-selector": "7.0.*", diff --git a/composer.lock b/composer.lock index bc06fd0..22d22b0 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": "c8031101044f75c20e905f33336bf7de", + "content-hash": "c1ff69a0c8b2b96c87a3465776574718", "packages": [ { "name": "composer/semver", @@ -7453,6 +7453,240 @@ } ], "packages-dev": [ + { + "name": "doctrine/data-fixtures", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/data-fixtures.git", + "reference": "bbcb74f2ac6dbe81a14b3c3687d7623490a0448f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/bbcb74f2ac6dbe81a14b3c3687d7623490a0448f", + "reference": "bbcb74f2ac6dbe81a14b3c3687d7623490a0448f", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^0.5.3 || ^1.0", + "doctrine/persistence": "^2.0|^3.0", + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.5 || >=5", + "doctrine/orm": "<2.14 || >=4", + "doctrine/phpcr-odm": "<1.3.0" + }, + "require-dev": { + "doctrine/annotations": "^1.12 || ^2", + "doctrine/coding-standard": "^12", + "doctrine/dbal": "^3.5 || ^4", + "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", + "doctrine/orm": "^2.14 || ^3", + "ext-sqlite3": "*", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6.13 || ^10.4.2", + "symfony/cache": "^5.4 || ^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6.3 || ^7", + "vimeo/psalm": "^5.9" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", + "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures", + "doctrine/orm": "For loading ORM fixtures", + "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\DataFixtures\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Data Fixtures for all Doctrine Object Managers", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database" + ], + "support": { + "issues": "https://github.com/doctrine/data-fixtures/issues", + "source": "https://github.com/doctrine/data-fixtures/tree/1.7.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdata-fixtures", + "type": "tidelift" + } + ], + "time": "2023-11-24T11:18:31+00:00" + }, + { + "name": "doctrine/doctrine-fixtures-bundle", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", + "reference": "87f5d53708a3855aa018bf0a00d0d4b0ef58a956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/87f5d53708a3855aa018bf0a00d0d4b0ef58a956", + "reference": "87f5d53708a3855aa018bf0a00d0d4b0ef58a956", + "shasum": "" + }, + "require": { + "doctrine/data-fixtures": "^1.3", + "doctrine/doctrine-bundle": "^2.2", + "doctrine/orm": "^2.14.0 || ^3.0", + "doctrine/persistence": "^2.4|^3.0", + "php": "^7.4 || ^8.0", + "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/deprecation-contracts": "^2.1|^3", + "symfony/doctrine-bridge": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0" + }, + "conflict": { + "doctrine/dbal": "< 3" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10.39", + "phpunit/phpunit": "^9.6.13", + "symfony/phpunit-bridge": "^6.3.6", + "vimeo/psalm": "^5.15" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\FixturesBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineFixturesBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "Fixture", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", + "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.6.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-fixtures-bundle", + "type": "tidelift" + } + ], + "time": "2024-05-02T18:06:53+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, { "name": "masterminds/html5", "version": "2.9.0", diff --git a/config/bundles.php b/config/bundles.php index 4e3a560..d5e1567 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -13,4 +13,5 @@ return [ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], ]; diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php new file mode 100644 index 0000000..12d5f40 --- /dev/null +++ b/src/DataFixtures/AppFixtures.php @@ -0,0 +1,34 @@ +text(); + $species = (new Species()) + ->setScientificName($name) + ->setVernacularName($name) + ->setRegion($faker->country()); + $date = DateTimeImmutable::createFromMutable($faker->dateTime()); + $post = (new Post()) + ->setFoundDate($date) + ->setPublicationDate($date) + ->setLatitude($faker->randomFloat()) + ->setLongitude($faker->randomFloat()) + ->setCommentary($faker->text()); + $manager->persist($species); + $manager->persist($post); + } + $manager->flush(); + } +} diff --git a/src/Entity/Post.php b/src/Entity/Post.php index ff4dc95..17ce70c 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -31,6 +31,9 @@ class Post #[ORM\Column(type: Types::TEXT)] private ?string $commentary = null; + + #[ORM\ManyToOne(inversedBy: 'posts')] + private ?Species $species = null; public function getId(): ?int { return $this->id; @@ -107,4 +110,16 @@ class Post return $this; } + + public function getSpecies(): ?Species + { + return $this->species; + } + + public function setSpecies(?Species $species): static + { + $this->species = $species; + + return $this; + } } diff --git a/src/Entity/Species.php b/src/Entity/Species.php index ed32f91..9243896 100644 --- a/src/Entity/Species.php +++ b/src/Entity/Species.php @@ -3,6 +3,8 @@ namespace App\Entity; use App\Repository\SpeciesRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: SpeciesRepository::class)] @@ -22,6 +24,17 @@ class Species #[ORM\Column(length: 255)] private ?string $region = null; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: Post::class, mappedBy: 'species')] + private Collection $posts; + + public function __construct() + { + $this->posts = new ArrayCollection(); + } + public function getId(): ?int { return $this->id; @@ -62,4 +75,34 @@ class Species return $this; } + + /** + * @return Collection + */ + public function getPosts(): Collection + { + return $this->posts; + } + + public function addPost(Post $post): static + { + if (!$this->posts->contains($post)) { + $this->posts->add($post); + $post->setSpecies($this); + } + + return $this; + } + + public function removePost(Post $post): static + { + if ($this->posts->removeElement($post)) { + // set the owning side to null (unless already changed) + if ($post->getSpecies() === $this) { + $post->setSpecies(null); + } + } + + return $this; + } } diff --git a/symfony.lock b/symfony.lock index d4b2e9b..e50c4b5 100644 --- a/symfony.lock +++ b/symfony.lock @@ -13,6 +13,18 @@ "src/Repository/.gitignore" ] }, + "doctrine/doctrine-fixtures-bundle": { + "version": "3.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "1f5514cfa15b947298df4d771e694e578d4c204d" + }, + "files": [ + "src/DataFixtures/AppFixtures.php" + ] + }, "doctrine/doctrine-migrations-bundle": { "version": "3.3", "recipe": { -- 2.43.0 From 999dbee2c6b12a7b9317b27e212184742910daa7 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Mon, 27 May 2024 08:03:08 +0200 Subject: [PATCH 02/29] Add PHPStan config --- .drone.yml | 30 +++++++ .gitignore | 4 + composer.json | 3 + composer.lock | 204 +++++++++++++++++++++++++++++++++++++++++++++- phpstan.dist.neon | 10 +++ symfony.lock | 12 +++ 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 .drone.yml create mode 100644 phpstan.dist.neon diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..c099272 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,30 @@ +kind: pipeline +name: default +type: docker + +on: + push: + +steps: + - name: install + image: composer/composer:2 + commands: + - composer install --no-interaction + + - name: lint + image: php:8.3 + commands: + - php bin/console lint:container + - php vendor/bin/phpstan analyse + depends_on: + - install + + - name: test + image: php:8.3 + commands: + - php bin/console make:migration + - php bin/console doctrine:migrations:migrate --no-interaction + - php bin/console doctrine:fixtures:load --no-interaction --env=test + - php bin/phpunit + depends_on: + - install diff --git a/.gitignore b/.gitignore index 3cb7d2c..ff3a72e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ /public/assets/ /assets/vendor/ ###< symfony/asset-mapper ### + +###> phpstan/phpstan ### +phpstan.neon +###< phpstan/phpstan ### diff --git a/composer.json b/composer.json index 39e0ac0..9b95112 100644 --- a/composer.json +++ b/composer.json @@ -97,6 +97,9 @@ "require-dev": { "doctrine/doctrine-fixtures-bundle": "^3.6", "fakerphp/faker": "^1.23", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-doctrine": "^1.4", + "phpstan/phpstan-symfony": "^1.4", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "7.0.*", "symfony/css-selector": "7.0.*", diff --git a/composer.lock b/composer.lock index 22d22b0..8dc6152 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": "c1ff69a0c8b2b96c87a3465776574718", + "content-hash": "963e7a72ac289ff9f4ad3c719ed70956", "packages": [ { "name": "composer/semver", @@ -7989,6 +7989,208 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.11.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec", + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-05-24T13:23:04+00:00" + }, + { + "name": "phpstan/phpstan-doctrine", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-doctrine.git", + "reference": "4b66f5c996865a6085983cc90b5c8a242d1959e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4b66f5c996865a6085983cc90b5c8a242d1959e7", + "reference": "4b66f5c996865a6085983cc90b5c8a242d1959e7", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "doctrine/collections": "<1.0", + "doctrine/common": "<2.7", + "doctrine/mongodb-odm": "<1.2", + "doctrine/orm": "<2.5", + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "cache/array-adapter": "^1.1", + "composer/semver": "^3.3.2", + "cweagans/composer-patches": "^1.7.3", + "doctrine/annotations": "^1.11 || ^2.0", + "doctrine/collections": "^1.6 || ^2.1", + "doctrine/common": "^2.7 || ^3.0", + "doctrine/dbal": "^2.13.8 || ^3.3.3", + "doctrine/lexer": "^2.0 || ^3.0", + "doctrine/mongodb-odm": "^1.3 || ^2.4.3", + "doctrine/orm": "^2.16.0", + "doctrine/persistence": "^2.2.1 || ^3.2", + "gedmo/doctrine-extensions": "^3.8", + "nesbot/carbon": "^2.49", + "nikic/php-parser": "^4.13.2", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.6.16", + "ramsey/uuid": "^4.2", + "symfony/cache": "^5.4" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Doctrine extensions for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-doctrine/issues", + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.0" + }, + "time": "2024-04-24T14:05:48+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "d530cfebba55763732bc2421f79d2576d9d7942f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/d530cfebba55763732bc2421f79d2576d9d7942f", + "reference": "d530cfebba55763732bc2421f79d2576d9d7942f", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^8.5.29 || ^9.5", + "psr/container": "1.0 || 1.1.1", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.1" + }, + "time": "2024-05-24T14:00:29+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.31", diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..afb7a06 --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,10 @@ +includes: + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-symfony/rules.neon + - vendor/phpstan/phpstan-doctrine/extension.neon + +parameters: + level: 8 + paths: + - src + - tests diff --git a/symfony.lock b/symfony.lock index e50c4b5..d9e695c 100644 --- a/symfony.lock +++ b/symfony.lock @@ -38,6 +38,18 @@ "migrations/.gitignore" ] }, + "phpstan/phpstan": { + "version": "1.11", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.0", + "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" + }, + "files": [ + "phpstan.dist.neon" + ] + }, "phpunit/phpunit": { "version": "9.6", "recipe": { -- 2.43.0 From ab95e3d28626d604287ca0b61159e8614bab103f Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 5 Jun 2024 10:17:54 +0200 Subject: [PATCH 03/29] Create a Docker image and deploy it --- .dockerignore | 36 ++++++++++++++++++++ .drone.yml | 37 +++++++++++++++++++++ Dockerfile | 32 ++++++++++++++++++ composer.json | 1 + composer.lock | 54 +++++++++++++++++++++++++++++- frankenphp/Caddyfile | 55 +++++++++++++++++++++++++++++++ frankenphp/conf.d/app.ini | 13 ++++++++ frankenphp/conf.d/app.prod.ini | 2 ++ frankenphp/start.sh | 4 +++ frankenphp/worker.Caddyfile | 4 +++ src/Controller/PostController.php | 20 +++++++++++ templates/base.html.twig | 2 +- templates/post/index.html.twig | 11 +++++++ 13 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 frankenphp/Caddyfile create mode 100644 frankenphp/conf.d/app.ini create mode 100644 frankenphp/conf.d/app.prod.ini create mode 100755 frankenphp/start.sh create mode 100644 frankenphp/worker.Caddyfile create mode 100644 src/Controller/PostController.php create mode 100644 templates/post/index.html.twig diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..787832b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +**/*.log +**/*.md +**/*.php~ +**/*.dist.php +**/*.dist +**/*.cache +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/compose.*.yaml +**/compose.*.yml +**/compose.yaml +**/compose.yml +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.drone.yml +.github/ +.gitea/ +docs/ +public/bundles/ +tests/ +var/ +vendor/ +.editorconfig +.env.*.local +.env.local +.env.local.php +.env.test diff --git a/.drone.yml b/.drone.yml index c099272..1acafc2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -28,3 +28,40 @@ steps: - php bin/phpunit depends_on: - install + + - name: docker-image + image: plugins/docker + settings: + registry: hub.codefirst.iut.uca.fr + repo: hub.codefirst.iut.uca.fr/clement.freville2/herbarium + username: + from_secret: REGISTRY_USER + password: + from_secret: REGISTRY_PASSWORD + cache_from: + - hub.codefirst.iut.uca.fr/clement.freville2/herbarium:latest + depends_on: + - lint + - test + when: + branch: + - main + - ci/* + + - name: deploy + image: hub.codefirst.iut.uca.fr/clement.freville2/codefirst-dockerproxy-clientdrone:latest + settings: + image: hub.codefirst.iut.uca.fr/clement.freville2/herbarium:latest + container: herbarium + command: create + overwrite: true + admins: clementfreville2 + environment: + # Disable HTTPS redirection as it served by a reverse proxy + CODEFIRST_CLIENTDRONE_ENV_SERVER_NAME: http://codefirst.iut.uca.fr + depends_on: + - docker-image + when: + branch: + - main + - ci/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a5e8f9d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM docker.io/dunglas/frankenphp:1-php8.3 + +RUN install-php-extensions \ + @composer \ + apcu \ + intl \ + opcache \ + zip \ + ; + +ENV APP_ENV=prod +ENV FRANKENPHP_CONFIG="import worker.Caddyfile" +ENV COMPOSER_ALLOW_SUPERUSER=1 + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +COPY frankenphp/conf.d/app.ini frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/ +COPY frankenphp/Caddyfile /etc/caddy/Caddyfile +COPY frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile +COPY frankenphp/start.sh . + +COPY composer.json composer.lock symfony.lock ./ +RUN composer install --no-cache --no-dev --no-autoloader --no-scripts --no-progress + +COPY . . +RUN composer dump-autoload --classmap-authoritative --no-dev \ + && composer dump-env prod \ + && composer run-script --no-dev post-install-cmd \ + ; +RUN rm -Rf frankenphp/ + +ENTRYPOINT [ "/app/start.sh" ] diff --git a/composer.json b/composer.json index 9b95112..a1821b9 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "doctrine/orm": "^3.1", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", + "runtime/frankenphp-symfony": "^0.2.0", "symfony/asset": "7.0.*", "symfony/asset-mapper": "7.0.*", "symfony/console": "7.0.*", diff --git a/composer.lock b/composer.lock index 8dc6152..d3f48ce 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": "963e7a72ac289ff9f4ad3c719ed70956", + "content-hash": "888e1953f86d0f1fe9b271b85aab0ed0", "packages": [ { "name": "composer/semver", @@ -2006,6 +2006,58 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "runtime/frankenphp-symfony", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-runtime/frankenphp-symfony.git", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/runtime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Runtime\\FrankenPhpSymfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.dev" + } + ], + "description": "FrankenPHP runtime for Symfony", + "support": { + "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", + "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-12-12T12:06:11+00:00" + }, { "name": "symfony/asset", "version": "v7.0.7", diff --git a/frankenphp/Caddyfile b/frankenphp/Caddyfile new file mode 100644 index 0000000..400d8cb --- /dev/null +++ b/frankenphp/Caddyfile @@ -0,0 +1,55 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + #worker /path/to/your/worker.php + {$FRANKENPHP_CONFIG} + } + + # https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm + order mercure after encode + order vulcain after reverse_proxy + order php_server before file_server + order php before file_server +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + log { + # # Redact the authorization query parameter that can be set by Mercure + # format filter { + # wrap console + # fields { + # uri query { + # replace authorization REDACTED + # } + # } + # } + output stdout + } + + root * public/ + encode zstd br gzip + + # Uncomment the following lines to enable Mercure and Vulcain modules + #mercure { + # # Transport to use (default to Bolt) + # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + # # Publisher JWT key + # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # # Subscriber JWT key + # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # # Allow anonymous subscribers (double-check that it's what you want) + # anonymous + # # Enable the subscription API (double-check that it's what you want) + # subscriptions + # # Extra directives + # {$MERCURE_EXTRA_DIRECTIVES} + #} + #vulcain + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + php_server +} diff --git a/frankenphp/conf.d/app.ini b/frankenphp/conf.d/app.ini new file mode 100644 index 0000000..79a17dd --- /dev/null +++ b/frankenphp/conf.d/app.ini @@ -0,0 +1,13 @@ +expose_php = 0 +date.timezone = UTC +apc.enable_cli = 1 +session.use_strict_mode = 1 +zend.detect_unicode = 0 + +; https://symfony.com/doc/current/performance.html +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +opcache.enable_file_override = 1 diff --git a/frankenphp/conf.d/app.prod.ini b/frankenphp/conf.d/app.prod.ini new file mode 100644 index 0000000..3bcaa71 --- /dev/null +++ b/frankenphp/conf.d/app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = root +opcache.preload = /app/config/preload.php diff --git a/frankenphp/start.sh b/frankenphp/start.sh new file mode 100755 index 0000000..b648810 --- /dev/null +++ b/frankenphp/start.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +php bin/console doctrine:migrations:migrate --no-interaction +exec frankenphp run --config /etc/caddy/Caddyfile diff --git a/frankenphp/worker.Caddyfile b/frankenphp/worker.Caddyfile new file mode 100644 index 0000000..d384ae4 --- /dev/null +++ b/frankenphp/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} diff --git a/src/Controller/PostController.php b/src/Controller/PostController.php new file mode 100644 index 0000000..0df3016 --- /dev/null +++ b/src/Controller/PostController.php @@ -0,0 +1,20 @@ +findAll(); + return $this->render('post/index.html.twig', [ + 'posts' => $posts, + ]); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index 233148c..bbf16f1 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -8,7 +8,7 @@ {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} + {% block importmap %}{% endblock %} {% endblock %} diff --git a/templates/post/index.html.twig b/templates/post/index.html.twig new file mode 100644 index 0000000..94f3756 --- /dev/null +++ b/templates/post/index.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block title %}Posts!{% endblock %} + +{% block body %} +{% for post in posts %} +
+ #{{ post.id }} trouvé le {{ post.foundDate | date("d/m/Y \\à H \\h") }} +
+{% endfor %} +{% endblock %} -- 2.43.0 From 6caa9a1ed08f1e696e188155cf0cd839bc675783 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 5 Jun 2024 10:34:13 +0200 Subject: [PATCH 04/29] Fix lint --- src/Entity/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/User.php b/src/Entity/User.php index b9e43ef..8e4dab6 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -87,7 +87,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface /** * @see PasswordAuthenticatedUserInterface */ - public function getPassword(): string + public function getPassword(): ?string { return $this->password; } -- 2.43.0 From 48bc5fd8c3e94ec08c058eeab1f4108a4bf4ea55 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 5 Jun 2024 10:36:04 +0200 Subject: [PATCH 05/29] Remove generated migrations --- .gitignore | 2 ++ migrations/Version20240522150738.php | 47 ---------------------------- 2 files changed, 2 insertions(+), 47 deletions(-) delete mode 100644 migrations/Version20240522150738.php diff --git a/.gitignore b/.gitignore index ff3a72e..be35b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ ###> phpstan/phpstan ### phpstan.neon ###< phpstan/phpstan ### + +migrations diff --git a/migrations/Version20240522150738.php b/migrations/Version20240522150738.php deleted file mode 100644 index 653ab20..0000000 --- a/migrations/Version20240522150738.php +++ /dev/null @@ -1,47 +0,0 @@ -addSql('CREATE TABLE post (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, found_date DATETIME NOT NULL --(DC2Type:datetime_immutable) - , publication_date DATETIME NOT NULL --(DC2Type:datetime_immutable) - , latitude DOUBLE PRECISION DEFAULT NULL, longitude DOUBLE PRECISION DEFAULT NULL, altitude DOUBLE PRECISION DEFAULT NULL, commentary CLOB NOT NULL)'); - $this->addSql('CREATE TABLE species (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, scientific_name VARCHAR(255) NOT NULL, vernacular_name VARCHAR(255) NOT NULL, region VARCHAR(255) NOT NULL)'); - $this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json) - , password VARCHAR(255) NOT NULL)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)'); - $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , available_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) - )'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); - $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE post'); - $this->addSql('DROP TABLE species'); - $this->addSql('DROP TABLE user'); - $this->addSql('DROP TABLE messenger_messages'); - } -} -- 2.43.0 From 22be991b8c52ef4c06d9280a32a0b931ca7cd04e Mon Sep 17 00:00:00 2001 From: syldium Date: Wed, 5 Jun 2024 17:37:53 +0200 Subject: [PATCH 06/29] Describe entities as API Platform resources --- .env | 4 + composer.json | 3 + composer.lock | 375 +++++++++++++++++- config/bundles.php | 3 + config/packages/api_platform.yaml | 19 + .../packages/dama_doctrine_test_bundle.yaml | 5 + config/packages/nelmio_cors.yaml | 10 + config/routes/api_platform.yaml | 4 + config/services.yaml | 4 + phpunit.xml.dist | 1 + src/Entity/Post.php | 29 ++ src/Entity/Species.php | 13 + src/Entity/User.php | 39 ++ src/State/UserPasswordHasher.php | 44 ++ symfony.lock | 38 ++ tests/Api/UserApiTest.php | 31 ++ 16 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 config/packages/api_platform.yaml create mode 100644 config/packages/dama_doctrine_test_bundle.yaml create mode 100644 config/packages/nelmio_cors.yaml create mode 100644 config/routes/api_platform.yaml create mode 100644 src/State/UserPasswordHasher.php create mode 100644 tests/Api/UserApiTest.php diff --git a/.env b/.env index 0b4b6b2..b743dd6 100755 --- a/.env +++ b/.env @@ -39,3 +39,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 ###> symfony/mailer ### # MAILER_DSN=null://null ###< symfony/mailer ### + +###> nelmio/cors-bundle ### +CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' +###< nelmio/cors-bundle ### diff --git a/composer.json b/composer.json index d59ef97..0a5a255 100644 --- a/composer.json +++ b/composer.json @@ -7,10 +7,12 @@ "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", + "api-platform/core": "^3.3", "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.12", "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.1", + "nelmio/cors-bundle": "^2.4", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", "runtime/frankenphp-symfony": "^0.2.0", @@ -97,6 +99,7 @@ } }, "require-dev": { + "dama/doctrine-test-bundle": "*", "doctrine/doctrine-fixtures-bundle": "^3.6", "fakerphp/faker": "^1.23", "phpstan/phpstan": "^1.11", diff --git a/composer.lock b/composer.lock index 9e8ea25..a528b05 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,196 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "888e1953f86d0f1fe9b271b85aab0ed0", + "content-hash": "cce9cbfaf4a49449e6431a8515f9d9eb", "packages": [ + { + "name": "api-platform/core", + "version": "v3.3.5", + "source": { + "type": "git", + "url": "https://github.com/api-platform/core.git", + "reference": "b5a93fb0bb855273aabb0807505ba61b68813246" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/core/zipball/b5a93fb0bb855273aabb0807505ba61b68813246", + "reference": "b5a93fb0bb855273aabb0807505ba61b68813246", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.0 || ^2.0", + "php": ">=8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^3.1", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/translation-contracts": "^3.3", + "symfony/web-link": "^6.4 || ^7.0", + "willdurand/negotiation": "^3.0" + }, + "conflict": { + "doctrine/common": "<3.2.2", + "doctrine/dbal": "<2.10", + "doctrine/mongodb-odm": "<2.4", + "doctrine/orm": "<2.14.0", + "doctrine/persistence": "<1.3", + "elasticsearch/elasticsearch": ">=8.0,<8.4", + "phpspec/prophecy": "<1.15", + "phpunit/phpunit": "<9.5", + "symfony/framework-bundle": "6.4.6 || 7.0.6", + "symfony/var-exporter": "<6.1.1" + }, + "require-dev": { + "behat/behat": "^3.11", + "behat/mink": "^1.9", + "doctrine/cache": "^1.11 || ^2.1", + "doctrine/common": "^3.2.2", + "doctrine/dbal": "^3.4.0", + "doctrine/doctrine-bundle": "^1.12 || ^2.0", + "doctrine/mongodb-odm": "^2.2", + "doctrine/mongodb-odm-bundle": "^4.0 || ^5.0", + "doctrine/orm": "^2.14 || ^3.0", + "elasticsearch/elasticsearch": "^7.11 || ^8.4", + "friends-of-behat/mink-browserkit-driver": "^1.3.1", + "friends-of-behat/mink-extension": "^2.2", + "friends-of-behat/symfony-extension": "^2.1", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "jangregor/phpstan-prophecy": "^1.0", + "justinrainbow/json-schema": "^5.2.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpdoc-parser": "^1.13", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-symfony": "^1.0", + "phpunit/phpunit": "^9.6", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^3.9.7 || ^4.0", + "ramsey/uuid-doctrine": "^1.4 || ^2.0", + "sebastian/comparator": "<5.0", + "soyuka/contexts": "v3.3.9", + "soyuka/pmu": "^0.0.2", + "soyuka/stubs-mongodb": "^1.0", + "symfony/asset": "^6.4 || ^7.0", + "symfony/browser-kit": "^6.4 || ^7.0", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/css-selector": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0.12", + "symfony/doctrine-bridge": "^6.4 || ^7.0", + "symfony/dom-crawler": "^6.4 || ^7.0", + "symfony/error-handler": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", + "symfony/finder": "^6.4 || ^7.0", + "symfony/form": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/http-client": "^6.4 || ^7.0", + "symfony/intl": "^6.4 || ^7.0", + "symfony/maker-bundle": "^1.24", + "symfony/mercure-bundle": "*", + "symfony/messenger": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4.1 || ^7.0", + "symfony/routing": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/security-core": "^6.4 || ^7.0", + "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/twig-bundle": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", + "twig/twig": "^1.42.3 || ^2.12 || ^3.0", + "webonyx/graphql-php": "^14.0 || ^15.0" + }, + "suggest": { + "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", + "elasticsearch/elasticsearch": "To support Elasticsearch.", + "ocramius/package-versions": "To display the API Platform's version in the debug bar.", + "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", + "psr/cache-implementation": "To use metadata caching.", + "ramsey/uuid": "To support Ramsey's UUID identifiers.", + "symfony/cache": "To have metadata caching when using Symfony integration.", + "symfony/config": "To load XML configuration files.", + "symfony/expression-language": "To use authorization features.", + "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/messenger": "To support messenger integration.", + "symfony/security": "To use authorization features.", + "symfony/twig-bundle": "To use the Swagger UI integration.", + "symfony/uid": "To support Symfony UUID/ULID identifiers.", + "symfony/web-profiler-bundle": "To use the data collector.", + "webonyx/graphql-php": "To support GraphQL." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3.x-dev" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "projects": [ + "api-platform/doctrine-common", + "api-platform/doctrine-orm", + "api-platform/doctrine-odm", + "api-platform/metadata", + "api-platform/json-schema", + "api-platform/elasticsearch", + "api-platform/jsonld", + "api-platform/hydra", + "api-platform/openapi", + "api-platform/graphql", + "api-platform/http-cache", + "api-platform/documentation", + "api-platform/parameter-validator", + "api-platform/ramsey-uuid", + "api-platform/serializer", + "api-platform/state", + "api-platform/symfony", + "api-platform/validator" + ] + }, + "autoload": { + "psr-4": { + "ApiPlatform\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr" + } + ], + "description": "Build a fully-featured hypermedia or GraphQL API in minutes!", + "homepage": "https://api-platform.com", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger" + ], + "support": { + "issues": "https://github.com/api-platform/core/issues", + "source": "https://github.com/api-platform/core/tree/v3.3.5" + }, + "time": "2024-05-29T05:48:47+00:00" + }, { "name": "composer/semver", "version": "3.4.0", @@ -1478,6 +1666,68 @@ ], "time": "2024-04-12T21:02:21+00:00" }, + { + "name": "nelmio/cors-bundle", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "78fcdb91f76b080a1008133def9c7f613833933d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/78fcdb91f76b080a1008133def9c7f613833933d", + "reference": "78fcdb91f76b080a1008133def9c7f613833933d", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.6", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.4.0" + }, + "time": "2023-11-30T16:41:19+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -7548,9 +7798,132 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" } ], "packages-dev": [ + { + "name": "dama/doctrine-test-bundle", + "version": "v8.2.0", + "source": { + "type": "git", + "url": "https://github.com/dmaicher/doctrine-test-bundle.git", + "reference": "1f81a280ea63f049d24e9c8ce00e557b18e0ff2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/1f81a280ea63f049d24e9c8ce00e557b18e0ff2f", + "reference": "1f81a280ea63f049d24e9c8ce00e557b18e0ff2f", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.3 || ^4.0", + "doctrine/doctrine-bundle": "^2.11.0", + "php": "^7.4 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.3 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0" + }, + "require-dev": { + "behat/behat": "^3.0", + "friendsofphp/php-cs-fixer": "^3.27", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "symfony/phpunit-bridge": "^6.3", + "symfony/process": "^5.4 || ^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Maicher", + "email": "mail@dmaicher.de" + } + ], + "description": "Symfony bundle to isolate doctrine database tests and improve test performance", + "keywords": [ + "doctrine", + "isolation", + "performance", + "symfony", + "testing", + "tests" + ], + "support": { + "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.2.0" + }, + "time": "2024-05-28T15:41:06+00:00" + }, { "name": "doctrine/data-fixtures", "version": "1.7.0", diff --git a/config/bundles.php b/config/bundles.php index 3584c3f..fbce34f 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,4 +15,7 @@ return [ Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true], + Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], + ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], + DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], ]; diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml new file mode 100644 index 0000000..1f8d9fb --- /dev/null +++ b/config/packages/api_platform.yaml @@ -0,0 +1,19 @@ +api_platform: + title: Hello API Platform + version: 1.0.0 + formats: + jsonld: ['application/ld+json'] + json: ['application/json'] + docs_formats: + jsonld: ['application/ld+json'] + jsonopenapi: ['application/vnd.openapi+json'] + html: ['text/html'] + defaults: + stateless: true + cache_headers: + vary: ['Content-Type', 'Authorization', 'Origin'] + extra_properties: + standard_put: true + rfc_7807_compliant_errors: true + keep_legacy_inflector: false + use_symfony_listeners: true diff --git a/config/packages/dama_doctrine_test_bundle.yaml b/config/packages/dama_doctrine_test_bundle.yaml new file mode 100644 index 0000000..3482cba --- /dev/null +++ b/config/packages/dama_doctrine_test_bundle.yaml @@ -0,0 +1,5 @@ +when@test: + dama_doctrine_test: + enable_static_connection: true + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml new file mode 100644 index 0000000..c766508 --- /dev/null +++ b/config/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/': null diff --git a/config/routes/api_platform.yaml b/config/routes/api_platform.yaml new file mode 100644 index 0000000..38f11cb --- /dev/null +++ b/config/routes/api_platform.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api diff --git a/config/services.yaml b/config/services.yaml index 2d6a76f..2021197 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -20,5 +20,9 @@ services: - '../src/Entity/' - '../src/Kernel.php' + App\State\UserPasswordHasher: + bind: + $processor: '@api_platform.doctrine.orm.state.persist_processor' + # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6c4bfed..4e6c0e2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -34,5 +34,6 @@ + diff --git a/src/Entity/Post.php b/src/Entity/Post.php index 17ce70c..d2f968b 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -2,38 +2,60 @@ namespace App\Entity; +use ApiPlatform\Metadata; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; use App\Repository\PostRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; #[ORM\Entity(repositoryClass: PostRepository::class)] +#[ORM\HasLifecycleCallbacks] +#[ApiResource( + operations: [new Metadata\Post(), new Metadata\Get(), new Metadata\Put(), new Metadata\Delete(), new Metadata\Patch()], + normalizationContext: ['groups' => ['post:collection:read', 'post:read']], +)] +#[GetCollection(normalizationContext: ['groups' => ['post:collection:read']])] class Post { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] + #[Groups(['post:collection:read'])] private ?int $id = null; #[ORM\Column] + #[Groups(['post:collection:read'])] private ?\DateTimeImmutable $foundDate = null; #[ORM\Column] + #[ApiProperty(writable: false)] + #[Groups(['post:collection:read'])] private ?\DateTimeImmutable $publicationDate = null; #[ORM\Column(nullable: true)] + #[Groups(['post:collection:read'])] private ?float $latitude = null; #[ORM\Column(nullable: true)] + #[Groups(['post:collection:read'])] private ?float $longitude = null; #[ORM\Column(nullable: true)] + #[Groups(['post:collection:read'])] private ?float $altitude = null; #[ORM\Column(type: Types::TEXT)] + #[Groups(['post:read'])] private ?string $commentary = null; #[ORM\ManyToOne(inversedBy: 'posts')] + #[ApiProperty(readableLink: false)] + #[Groups(['post:collection:read'])] private ?Species $species = null; + public function getId(): ?int { return $this->id; @@ -122,4 +144,11 @@ class Post return $this; } + + #[ORM\PrePersist] + #[ORM\PreUpdate] + public function setPublicationDateValue(): void + { + $this->publicationDate = new \DateTimeImmutable(); + } } diff --git a/src/Entity/Species.php b/src/Entity/Species.php index 9243896..e2e1b78 100644 --- a/src/Entity/Species.php +++ b/src/Entity/Species.php @@ -2,32 +2,45 @@ namespace App\Entity; +use ApiPlatform\Metadata; +use ApiPlatform\Metadata\ApiResource; use App\Repository\SpeciesRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; #[ORM\Entity(repositoryClass: SpeciesRepository::class)] +#[ApiResource( + operations: [new Metadata\Post(), new Metadata\Get(), new Metadata\Put(), new Metadata\Delete(), new Metadata\Patch()], + normalizationContext: ['groups' => ['species:collection:read', 'species:read']], +)] +#[Metadata\GetCollection(normalizationContext: ['groups' => ['species:collection:read']])] class Species { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] + #[Groups(['species:collection:read'])] private ?int $id = null; #[ORM\Column(length: 255)] + #[Groups(['species:collection:read'])] private ?string $scientific_name = null; #[ORM\Column(length: 255)] + #[Groups(['species:collection:read'])] private ?string $vernacular_name = null; #[ORM\Column(length: 255)] + #[Groups(['species:collection:read'])] private ?string $region = null; /** * @var Collection */ #[ORM\OneToMany(targetEntity: Post::class, mappedBy: 'species')] + #[Groups(['species:read'])] private Collection $posts; public function __construct() diff --git a/src/Entity/User.php b/src/Entity/User.php index 8e4dab6..e924817 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -2,15 +2,36 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Put; use App\Repository\UserRepository; +use App\State\UserPasswordHasher; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] #[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')] +#[ApiResource( + operations: [ + new GetCollection(), + new \ApiPlatform\Metadata\Post(validationContext: ['groups' => ['Default', 'user:create']], processor: UserPasswordHasher::class), + new Get(), + new Put(processor: UserPasswordHasher::class), + new Patch(processor: UserPasswordHasher::class), + new Delete(), + ], + normalizationContext: ['groups' => ['user:read']], + denormalizationContext: ['groups' => ['user:create', 'user:update']], +)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] @@ -18,6 +39,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?int $id = null; + #[Assert\Email] + #[Groups(['user:read', 'user:create', 'user:update'])] #[ORM\Column(length: 180)] private ?string $email = null; @@ -33,6 +56,10 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?string $password = null; + #[Assert\NotBlank(groups: ['user:create'])] + #[Groups(['user:create', 'user:update'])] + private ?string $plainPassword = null; + public function getId(): ?int { return $this->id; @@ -99,6 +126,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + public function getPlainPassword(): ?string + { + return $this->plainPassword; + } + + public function setPlainPassword(?string $plainPassword): self + { + $this->plainPassword = $plainPassword; + + return $this; + } + /** * @see UserInterface */ diff --git a/src/State/UserPasswordHasher.php b/src/State/UserPasswordHasher.php new file mode 100644 index 0000000..0fc91be --- /dev/null +++ b/src/State/UserPasswordHasher.php @@ -0,0 +1,44 @@ + + */ +final readonly class UserPasswordHasher implements ProcessorInterface +{ + /** + * @param ProcessorInterface $processor + * @param UserPasswordHasherInterface $passwordHasher + */ + public function __construct( + private ProcessorInterface $processor, + private UserPasswordHasherInterface $passwordHasher, + ) + { + } + + /** + * @param User $data + */ + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User + { + if (!$data->getPlainPassword()) { + return $this->processor->process($data, $operation, $uriVariables, $context); + } + + $hashedPassword = $this->passwordHasher->hashPassword( + $data, + $data->getPlainPassword() + ); + $data->setPassword($hashedPassword); + $data->eraseCredentials(); + + return $this->processor->process($data, $operation, $uriVariables, $context); + } +} diff --git a/symfony.lock b/symfony.lock index 59594a0..5e18ec6 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,4 +1,30 @@ { + "api-platform/core": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.3", + "ref": "74b45ac570c57eb1fbe56c984091a9ff87e18bab" + }, + "files": [ + "config/packages/api_platform.yaml", + "config/routes/api_platform.yaml", + "src/ApiResource/.gitignore" + ] + }, + "dama/doctrine-test-bundle": { + "version": "8.2", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "7.2", + "ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70" + }, + "files": [ + "config/packages/dama_doctrine_test_bundle.yaml" + ] + }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { @@ -38,6 +64,18 @@ "migrations/.gitignore" ] }, + "nelmio/cors-bundle": { + "version": "2.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.5", + "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" + }, + "files": [ + "config/packages/nelmio_cors.yaml" + ] + }, "phpstan/phpstan": { "version": "1.11", "recipe": { diff --git a/tests/Api/UserApiTest.php b/tests/Api/UserApiTest.php new file mode 100644 index 0000000..e5e997f --- /dev/null +++ b/tests/Api/UserApiTest.php @@ -0,0 +1,31 @@ +request('POST', '/api/users', [ + 'json' => [ + 'email' => 'test@test.com', + 'plainPassword' => 'password', + ] + ]); + + $this->assertResponseStatusCodeSame(201); + $this->assertResponseHasHeader('Content-Location'); + + $location = $response->getHeaders()['content-location'][0]; + static::createClient()->request('GET', $location); + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + '@context' => '/api/contexts/User', + '@id' => $location, + '@type' => 'User', + 'email' => 'test@test.com', + ]); + } +} -- 2.43.0 From 8789da089d4c20db120995907e89dcc05dbca824 Mon Sep 17 00:00:00 2001 From: syldium Date: Wed, 5 Jun 2024 18:06:08 +0200 Subject: [PATCH 07/29] Compile assets map --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index a5e8f9d..e5338da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ COPY . . RUN composer dump-autoload --classmap-authoritative --no-dev \ && composer dump-env prod \ && composer run-script --no-dev post-install-cmd \ + && php bin/console asset-map:compile \ ; RUN rm -Rf frankenphp/ -- 2.43.0 From 12f3b910444c7c1455eafe689277eb2809fa8e0a Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 5 Jun 2024 18:53:02 +0200 Subject: [PATCH 08/29] Set assets base path --- .drone.yml | 2 ++ .env | 2 ++ config/packages/framework.yaml | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.drone.yml b/.drone.yml index 1acafc2..ddd4266 100644 --- a/.drone.yml +++ b/.drone.yml @@ -59,6 +59,8 @@ steps: environment: # Disable HTTPS redirection as it served by a reverse proxy CODEFIRST_CLIENTDRONE_ENV_SERVER_NAME: http://codefirst.iut.uca.fr + CODEFIRST_CLIENTDRONE_ENV_CORS_ALLOW_ORIGIN: https://codefirst.iut.uca.fr + CODEFIRST_CLIENTDRONE_ENV_ASSETS_BASE_PATH: /containers/clementfreville2-herbarium depends_on: - docker-image when: diff --git a/.env b/.env index b743dd6..1443282 100755 --- a/.env +++ b/.env @@ -14,6 +14,8 @@ # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration +ASSETS_BASE_PATH=/ + ###> symfony/framework-bundle ### APP_ENV=dev APP_SECRET=654d4972fc24500c7b43763fc3b3efa7 diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 877eb25..a09ef0c 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -3,6 +3,9 @@ framework: secret: '%env(APP_SECRET)%' #csrf_protection: true + assets: + base_path: '%env(ASSETS_BASE_PATH)%' + # Note that the session will be started ONLY if you read or write from it. session: true -- 2.43.0 From b94fd9a2a0b756014ba3641c877123b0b319100a Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Thu, 6 Jun 2024 20:08:47 +0200 Subject: [PATCH 09/29] Add more tests --- src/Entity/Post.php | 6 +- tests/Api/PostApiTest.php | 114 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 tests/Api/PostApiTest.php diff --git a/src/Entity/Post.php b/src/Entity/Post.php index d2f968b..0b54550 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -2,6 +2,7 @@ namespace App\Entity; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -18,6 +19,7 @@ use Symfony\Component\Serializer\Attribute\Groups; normalizationContext: ['groups' => ['post:collection:read', 'post:read']], )] #[GetCollection(normalizationContext: ['groups' => ['post:collection:read']])] +#[Metadata\ApiFilter(filterClass: SearchFilter::class, properties: ['species' => 'exact'])] class Post { #[ORM\Id] @@ -149,6 +151,8 @@ class Post #[ORM\PreUpdate] public function setPublicationDateValue(): void { - $this->publicationDate = new \DateTimeImmutable(); + if ($this->publicationDate === null) { + $this->publicationDate = new \DateTimeImmutable(); + } } } diff --git a/tests/Api/PostApiTest.php b/tests/Api/PostApiTest.php new file mode 100644 index 0000000..9304115 --- /dev/null +++ b/tests/Api/PostApiTest.php @@ -0,0 +1,114 @@ +getContainer()->get('doctrine')->getManager(); + $species = (new Species()) + ->setVernacularName('Oak') + ->setScientificName('Quercus') + ->setRegion('Europe'); + $post = (new Post()) + ->setPublicationDate(new \DateTimeImmutable('2024-06-07')) + ->setFoundDate(new \DateTimeImmutable('2024-06-06')) + ->setCommentary("maple") + ->setSpecies($species); + $manager->persist($species); + $manager->persist($post); + $manager->flush(); + $this->speciesId = $species->getId(); + $this->postId = $post->getId(); + } + + public function testFindInSpecies(): void + { + $response = static::createClient()->request('GET', '/api/species/' . $this->speciesId); + + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + '@context' => '/api/contexts/Species', + '@id' => '/api/species/' . $this->speciesId, + '@type' => 'Species', + 'id' => $this->speciesId, + 'vernacular_name' => 'Oak', + 'scientific_name' => 'Quercus', + 'region' => 'Europe', + 'posts' => ['/api/posts/' . $this->postId], + ]); + } + + public function testGetExisting(): void + { + $response = static::createClient()->request('GET', '/api/posts/' . $this->postId); + + $this->assertResponseIsSuccessful(); + $this->assertJsonEquals([ + '@context' => '/api/contexts/Post', + '@id' => '/api/posts/' . $this->postId, + '@type' => 'Post', + 'id' => $this->postId, + 'foundDate' => '2024-06-06T00:00:00+00:00', + 'publicationDate' => '2024-06-07T00:00:00+00:00', + 'commentary' => 'maple', + 'species' => '/api/species/' . $this->speciesId, + ]); + } + + public function testFilterBySpecies(): void + { + $response = static::createClient()->request('GET', '/api/posts', [ + 'query' => [ + 'species' => $this->speciesId, + ], + ]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@context' => '/api/contexts/Post', + '@id' => '/api/posts', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 1, + 'hydra:member' => [ + [ + '@type' => 'Post', + 'id' => $this->postId, + 'foundDate' => '2024-06-06T00:00:00+00:00', + 'species' => '/api/species/' . $this->speciesId, + ], + ], + ]); + } + + public function testPostSetPublicationDate(): void + { + $response = static::createClient()->request('POST', '/api/posts', [ + 'json' => [ + 'foundDate' => '2024-06-06', + 'publicationDate' => '2024-06-07', + 'commentary' => 'maple', + 'species' => '/api/species/' . $this->speciesId, + ], + ]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@context' => '/api/contexts/Post', + '@type' => 'Post', + 'foundDate' => '2024-06-06T00:00:00+00:00', + 'commentary' => 'maple', + 'species' => '/api/species/' . $this->speciesId, + ]); + $this->assertArrayHasKey('publicationDate', $response->toArray(false)); + } +} -- 2.43.0 From 62da326e7508dfdca601a3d70938b9534ad729e2 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 7 Jun 2024 09:10:06 +0200 Subject: [PATCH 10/29] Create an empty controllers directory --- assets/controllers/.gitignore | 0 templates/base.html.twig | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/controllers/.gitignore diff --git a/assets/controllers/.gitignore b/assets/controllers/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/templates/base.html.twig b/templates/base.html.twig index bbf16f1..233148c 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -8,7 +8,7 @@ {% endblock %} {% block javascripts %} - {% block importmap %}{% endblock %} + {% block importmap %}{{ importmap('app') }}{% endblock %} {% endblock %} -- 2.43.0 From 82a3f69fa42598e2322fbc37f06f3ecf0c72e972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20LAPORTE?= Date: Fri, 7 Jun 2024 15:27:55 +0200 Subject: [PATCH 11/29] View (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Création de la page species et detail specie Co-authored-by: Clément Laporte Co-authored-by: clfreville2 Reviewed-on: https://codefirst.iut.uca.fr/git/clement.freville2/herbarium/pulls/8 Co-authored-by: Clément LAPORTE Co-committed-by: Clément LAPORTE --- src/Controller/SpeciesController.php | 29 +++++++++++++++++++ src/DataFixtures/AppFixtures.php | 3 +- templates/species/detail.html.twig | 43 ++++++++++++++++++++++++++++ templates/species/index.html.twig | 29 +++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/Controller/SpeciesController.php create mode 100644 templates/species/detail.html.twig create mode 100644 templates/species/index.html.twig diff --git a/src/Controller/SpeciesController.php b/src/Controller/SpeciesController.php new file mode 100644 index 0000000..e727c11 --- /dev/null +++ b/src/Controller/SpeciesController.php @@ -0,0 +1,29 @@ +findAll(); + return $this->render('species/index.html.twig', [ + 'species' => $species, + ]); + } + + #[Route('/species/{id}', name: 'app_species_detail')] + public function detail(Species $specie): Response + { + return $this->render('species/detail.html.twig', [ + 'specie' => $specie, + ]); + } +} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 12d5f40..2fbb47f 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -25,7 +25,8 @@ class AppFixtures extends Fixture ->setPublicationDate($date) ->setLatitude($faker->randomFloat()) ->setLongitude($faker->randomFloat()) - ->setCommentary($faker->text()); + ->setCommentary($faker->text()) + -> setSpecies($species); $manager->persist($species); $manager->persist($post); } diff --git a/templates/species/detail.html.twig b/templates/species/detail.html.twig new file mode 100644 index 0000000..d6a5ebf --- /dev/null +++ b/templates/species/detail.html.twig @@ -0,0 +1,43 @@ +{% extends 'base.html.twig' %} + +{% block title %}Herbarium - Détail de l'espèces{% endblock %} + +{% block body %} + + +
+

{{ specie.vernacularName }}

+

+ 🔬 Nom Scientifique : {{ specie.scientificName }}
+ 📍 Region : {{ specie.region }} +

+
+

Posts :

+ + {% for post in specie.posts %} +
+
+

+ + 📅 {{ post.publicationDate | date }} + +

+
+
+ 📍 géolocalisation :
+ - Longitude : {{ post.longitude }}
+ - Latitude : {{ post.latitude }}
+ - Altitude : {{ post.altitude }}

+ + 💬 Commentaire :
+ - {{ post.getCommentary }} +
+
+ {% endfor %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/templates/species/index.html.twig b/templates/species/index.html.twig new file mode 100644 index 0000000..666ad41 --- /dev/null +++ b/templates/species/index.html.twig @@ -0,0 +1,29 @@ +{% extends 'base.html.twig' %} + +{% block title %}Herbarium - Espèces{% endblock %} + +{% block body %} + + +
+

Liste des espèces

+ +
+ {% for specie in species %} +
+ + 🌿 {{ specie.getVernacularName }} + +
+
+ 🔬 Nom Scientifique : {{ specie.getScientificName }}
+ 📍 Region : {{ specie.getRegion }} +

+ {% endfor %} +
+ +
+{% endblock %} -- 2.43.0 From 49d60871c981b39dce1e7726b4691af63ab290bc Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 7 Jun 2024 17:49:12 +0200 Subject: [PATCH 12/29] Add post and species forms Squashed commit of the following: Author: Hugo PRADIER Author: bastien ollier Author: clfreville2 Reviewed on #7 --- composer.json | 2 +- composer.lock | 29 ++--- phpstan.dist.neon | 1 - src/Controller/PostController.php | 68 ++++++++++- src/Controller/RegistrationController.php | 6 +- src/Controller/SpeciesController.php | 72 ++++++++++-- src/DataFixtures/AppFixtures.php | 14 ++- src/Entity/Post.php | 3 + src/Entity/Species.php | 4 + src/Form/PostType.php | 38 ++++++ src/Form/SpeciesType.php | 27 +++++ src/Repository/UserRepository.php | 10 ++ templates/post/_delete_form.html.twig | 4 + templates/post/_form.html.twig | 4 + templates/post/edit.html.twig | 13 +++ templates/post/index.html.twig | 11 -- templates/post/new.html.twig | 11 ++ templates/post/show.html.twig | 46 ++++++++ templates/post/table.html.twig | 45 +++++++ templates/species/_delete_form.html.twig | 4 + templates/species/_form.html.twig | 4 + templates/species/detail.html.twig | 43 ------- templates/species/edit.html.twig | 13 +++ templates/species/index.html.twig | 11 +- templates/species/new.html.twig | 11 ++ templates/species/show.html.twig | 36 ++++++ templates/species/table.html.twig | 39 +++++++ tests/Controller/PostControllerTest.php | 128 ++++++++++++++++++++ tests/Controller/SpeciesControllerTest.php | 129 +++++++++++++++++++++ 29 files changed, 740 insertions(+), 86 deletions(-) create mode 100644 src/Form/PostType.php create mode 100644 src/Form/SpeciesType.php create mode 100644 templates/post/_delete_form.html.twig create mode 100644 templates/post/_form.html.twig create mode 100644 templates/post/edit.html.twig delete mode 100644 templates/post/index.html.twig create mode 100644 templates/post/new.html.twig create mode 100644 templates/post/show.html.twig create mode 100644 templates/post/table.html.twig create mode 100644 templates/species/_delete_form.html.twig create mode 100644 templates/species/_form.html.twig delete mode 100644 templates/species/detail.html.twig create mode 100644 templates/species/edit.html.twig create mode 100644 templates/species/new.html.twig create mode 100644 templates/species/show.html.twig create mode 100644 templates/species/table.html.twig create mode 100644 tests/Controller/PostControllerTest.php create mode 100644 tests/Controller/SpeciesControllerTest.php diff --git a/composer.json b/composer.json index 0a5a255..c02ecbd 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,7 @@ "symfony/browser-kit": "7.0.*", "symfony/css-selector": "7.0.*", "symfony/debug-bundle": "7.0.*", - "symfony/maker-bundle": "^1.0", + "symfony/maker-bundle": "^1.59", "symfony/phpunit-bridge": "^7.0", "symfony/stopwatch": "7.0.*", "symfony/web-profiler-bundle": "7.0.*" diff --git a/composer.lock b/composer.lock index a528b05..12a1306 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": "cce9cbfaf4a49449e6431a8515f9d9eb", + "content-hash": "9df70d112adfdfa4fbcde08b504d0bb9", "packages": [ { "name": "api-platform/core", @@ -3757,16 +3757,16 @@ }, { "name": "symfony/form", - "version": "v7.0.7", + "version": "v7.0.8", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "b4df6a399a2b03782a0163807239db342659f54f" + "reference": "1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/b4df6a399a2b03782a0163807239db342659f54f", - "reference": "b4df6a399a2b03782a0163807239db342659f54f", + "url": "https://api.github.com/repos/symfony/form/zipball/1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb", + "reference": "1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb", "shasum": "" }, "require": { @@ -3833,7 +3833,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.0.7" + "source": "https://github.com/symfony/form/tree/v7.0.8" }, "funding": [ { @@ -3849,7 +3849,7 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-05-31T14:55:39+00:00" }, { "name": "symfony/framework-bundle", @@ -7137,16 +7137,16 @@ }, { "name": "symfony/validator", - "version": "v7.0.7", + "version": "v7.0.8", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "ab4e75b9d23ba70e78480aecbe4d8da15adf10eb" + "reference": "23af65dff1f4dfee9aab3a0123a243e40fa3d9cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/ab4e75b9d23ba70e78480aecbe4d8da15adf10eb", - "reference": "ab4e75b9d23ba70e78480aecbe4d8da15adf10eb", + "url": "https://api.github.com/repos/symfony/validator/zipball/23af65dff1f4dfee9aab3a0123a243e40fa3d9cf", + "reference": "23af65dff1f4dfee9aab3a0123a243e40fa3d9cf", "shasum": "" }, "require": { @@ -7191,7 +7191,8 @@ "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7211,7 +7212,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.0.7" + "source": "https://github.com/symfony/validator/tree/v7.0.8" }, "funding": [ { @@ -7227,7 +7228,7 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-06-02T15:49:03+00:00" }, { "name": "symfony/var-dumper", diff --git a/phpstan.dist.neon b/phpstan.dist.neon index afb7a06..b7cb307 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -7,4 +7,3 @@ parameters: level: 8 paths: - src - - tests diff --git a/src/Controller/PostController.php b/src/Controller/PostController.php index 0df3016..8bbe909 100644 --- a/src/Controller/PostController.php +++ b/src/Controller/PostController.php @@ -2,19 +2,85 @@ namespace App\Controller; +use App\Entity\Post; +use App\Form\PostType; use App\Repository\PostRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; class PostController extends AbstractController { #[Route('/', name: 'app_posts')] + #[Route('/post', name: 'app_post_index', methods: ['GET'])] public function index(PostRepository $repository): Response { $posts = $repository->findAll(); - return $this->render('post/index.html.twig', [ + return $this->render('post/table.html.twig', [ 'posts' => $posts, ]); } + + #[Route('/post/new', name: 'app_post_new', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $post = new Post(); + $form = $this->createForm(PostType::class, $post); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($post); + $entityManager->flush(); + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('post/new.html.twig', [ + 'post' => $post, + 'form' => $form, + ]); + } + + #[Route('/post/{id}', name: 'app_post_show', methods: ['GET'])] + public function show(Post $post): Response + { + return $this->render('post/show.html.twig', [ + 'post' => $post, + ]); + } + + #[Route('/post/{id}/edit', name: 'app_post_edit', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function edit(Request $request, Post $post, EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(PostType::class, $post); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('post/edit.html.twig', [ + 'post' => $post, + 'form' => $form, + ]); + } + + #[Route('/post/{id}', name: 'app_post_delete', methods: ['POST'])] + #[IsGranted('ROLE_USER')] + public function delete(Request $request, Post $post, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$post->getId(), (string) $request->getPayload()->get('_token'))) { + $entityManager->remove($post); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } } diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 6bfc237..43043a9 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -34,7 +34,11 @@ class RegistrationController extends AbstractController // do anything else you need here, like send an email - return $this->redirectToRoute('_profiler_home'); + return $this->redirectToRoute('app_login'); + } + + if ($this->getUser()) { + return $this->redirectToRoute('app_posts'); } return $this->render('registration/register.html.twig', [ diff --git a/src/Controller/SpeciesController.php b/src/Controller/SpeciesController.php index e727c11..a495a0f 100644 --- a/src/Controller/SpeciesController.php +++ b/src/Controller/SpeciesController.php @@ -3,27 +3,83 @@ namespace App\Controller; use App\Entity\Species; +use App\Form\SpeciesType; use App\Repository\SpeciesRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; +#[Route('/species')] class SpeciesController extends AbstractController { - #[Route('/species', name: 'app_species')] - public function index(SpeciesRepository $repository): Response + #[Route('/', name: 'app_species_index', methods: ['GET'])] + public function table(SpeciesRepository $speciesRepository): Response { - $species = $repository->findAll(); - return $this->render('species/index.html.twig', [ + return $this->render('species/table.html.twig', [ + 'species' => $speciesRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_species_new', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $species = new Species(); + $form = $this->createForm(SpeciesType::class, $species); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($species); + $entityManager->flush(); + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('species/new.html.twig', [ + 'species' => $species, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_species_show', methods: ['GET'])] + public function show(Species $species): Response + { + return $this->render('species/show.html.twig', [ 'species' => $species, ]); } - #[Route('/species/{id}', name: 'app_species_detail')] - public function detail(Species $specie): Response + #[Route('/{id}/edit', name: 'app_species_edit', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function edit(Request $request, Species $species, EntityManagerInterface $entityManager): Response { - return $this->render('species/detail.html.twig', [ - 'specie' => $specie, + $form = $this->createForm(SpeciesType::class, $species); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('species/edit.html.twig', [ + 'species' => $species, + 'form' => $form, ]); } + + #[Route('/{id}', name: 'app_species_delete', methods: ['POST'])] + #[IsGranted('ROLE_USER')] + public function delete(Request $request, Species $species, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$species->getId(), (string) $request->getPayload()->get('_token'))) { + $entityManager->remove($species); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } } diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 2fbb47f..6bff9e2 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -4,17 +4,29 @@ namespace App\DataFixtures; use App\Entity\Post; use App\Entity\Species; +use App\Entity\User; use DateTimeImmutable; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AppFixtures extends Fixture { + public function __construct( + private readonly UserPasswordHasherInterface $passwordHasher + ) + { + } + public function load(ObjectManager $manager): void { + $user = (new User())->setEmail('test@test.fr'); + $user->setPassword($this->passwordHasher->hashPassword($user, 'password')); + $manager->persist($user); + $faker = \Faker\Factory::create(); for ($i = 0; $i < 20; ++$i) { - $name = $faker->text(); + $name = $faker->name(); $species = (new Species()) ->setScientificName($name) ->setVernacularName($name) diff --git a/src/Entity/Post.php b/src/Entity/Post.php index 0b54550..22e346a 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -11,6 +11,7 @@ use App\Repository\PostRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: PostRepository::class)] #[ORM\HasLifecycleCallbacks] @@ -30,6 +31,7 @@ class Post #[ORM\Column] #[Groups(['post:collection:read'])] + #[Assert\NotBlank] private ?\DateTimeImmutable $foundDate = null; #[ORM\Column] @@ -51,6 +53,7 @@ class Post #[ORM\Column(type: Types::TEXT)] #[Groups(['post:read'])] + #[Assert\NotBlank] private ?string $commentary = null; #[ORM\ManyToOne(inversedBy: 'posts')] diff --git a/src/Entity/Species.php b/src/Entity/Species.php index e2e1b78..285fe6c 100644 --- a/src/Entity/Species.php +++ b/src/Entity/Species.php @@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: SpeciesRepository::class)] #[ApiResource( @@ -26,14 +27,17 @@ class Species #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $scientific_name = null; #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $vernacular_name = null; #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $region = null; /** diff --git a/src/Form/PostType.php b/src/Form/PostType.php new file mode 100644 index 0000000..bea7e6f --- /dev/null +++ b/src/Form/PostType.php @@ -0,0 +1,38 @@ +add('foundDate', null, [ + 'widget' => 'single_text', + ]) + ->add('latitude') + ->add('longitude') + ->add('altitude') + ->add('commentary') + ->add('species', EntityType::class, [ + 'class' => Species::class, + 'choice_label' => 'scientific_name', + ]) + + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Post::class, + ]); + } +} diff --git a/src/Form/SpeciesType.php b/src/Form/SpeciesType.php new file mode 100644 index 0000000..ae3b919 --- /dev/null +++ b/src/Form/SpeciesType.php @@ -0,0 +1,27 @@ +add('scientific_name') + ->add('vernacular_name') + ->add('region') + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Species::class, + ]); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 4f2804e..e861224 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -33,6 +33,16 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader $this->getEntityManager()->flush(); } + public function findOneByEmail(string $email): ?User + { + return $this->createQueryBuilder('u') + ->andWhere('u.email = :email') + ->setParameter('email', $email) + ->getQuery() + ->getOneOrNullResult() + ; + } + // /** // * @return User[] Returns an array of User objects // */ diff --git a/templates/post/_delete_form.html.twig b/templates/post/_delete_form.html.twig new file mode 100644 index 0000000..1223c9b --- /dev/null +++ b/templates/post/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/post/_form.html.twig b/templates/post/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/post/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/post/edit.html.twig b/templates/post/edit.html.twig new file mode 100644 index 0000000..16b6b5f --- /dev/null +++ b/templates/post/edit.html.twig @@ -0,0 +1,13 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit Post{% endblock %} + +{% block body %} +

Edit Post

+ + {{ include('post/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('post/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/post/index.html.twig b/templates/post/index.html.twig deleted file mode 100644 index 94f3756..0000000 --- a/templates/post/index.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Posts!{% endblock %} - -{% block body %} -{% for post in posts %} -
- #{{ post.id }} trouvé le {{ post.foundDate | date("d/m/Y \\à H \\h") }} -
-{% endfor %} -{% endblock %} diff --git a/templates/post/new.html.twig b/templates/post/new.html.twig new file mode 100644 index 0000000..b8bc236 --- /dev/null +++ b/templates/post/new.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block title %}New Post{% endblock %} + +{% block body %} +

Create new Post

+ + {{ include('post/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/templates/post/show.html.twig b/templates/post/show.html.twig new file mode 100644 index 0000000..a332dba --- /dev/null +++ b/templates/post/show.html.twig @@ -0,0 +1,46 @@ +{% extends 'base.html.twig' %} + +{% block title %}Post{% endblock %} + +{% block body %} +

Post

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Id{{ post.id }}
FoundDate{{ post.foundDate ? post.foundDate|date('Y-m-d H:i:s') : '' }}
PublicationDate{{ post.publicationDate ? post.publicationDate|date('Y-m-d H:i:s') : '' }}
Latitude{{ post.latitude }}
Longitude{{ post.longitude }}
Altitude{{ post.altitude }}
Commentary{{ post.commentary }}
+ + back to list + + edit + + {{ include('post/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/post/table.html.twig b/templates/post/table.html.twig new file mode 100644 index 0000000..3a5db93 --- /dev/null +++ b/templates/post/table.html.twig @@ -0,0 +1,45 @@ +{% extends 'base.html.twig' %} + +{% block title %}Post index{% endblock %} + +{% block body %} +

Post index

+ + + + + + + + + + + + + + + + {% for post in posts %} + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
IdFoundDatePublicationDateLatitudeLongitudeAltitudeCommentaryactions
{{ post.id }}{{ post.foundDate ? post.foundDate|date('Y-m-d H:i:s') : '' }}{{ post.publicationDate ? post.publicationDate|date('Y-m-d H:i:s') : '' }}{{ post.latitude }}{{ post.longitude }}{{ post.altitude }}{{ post.commentary }} + show + edit +
no records found
+ + Create new +{% endblock %} diff --git a/templates/species/_delete_form.html.twig b/templates/species/_delete_form.html.twig new file mode 100644 index 0000000..9e7c05a --- /dev/null +++ b/templates/species/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/species/_form.html.twig b/templates/species/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/species/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/species/detail.html.twig b/templates/species/detail.html.twig deleted file mode 100644 index d6a5ebf..0000000 --- a/templates/species/detail.html.twig +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Herbarium - Détail de l'espèces{% endblock %} - -{% block body %} - - -
-

{{ specie.vernacularName }}

-

- 🔬 Nom Scientifique : {{ specie.scientificName }}
- 📍 Region : {{ specie.region }} -

-
-

Posts :

- - {% for post in specie.posts %} -
-
-

- - 📅 {{ post.publicationDate | date }} - -

-
-
- 📍 géolocalisation :
- - Longitude : {{ post.longitude }}
- - Latitude : {{ post.latitude }}
- - Altitude : {{ post.altitude }}

- - 💬 Commentaire :
- - {{ post.getCommentary }} -
-
- {% endfor %} -
- -
-{% endblock %} \ No newline at end of file diff --git a/templates/species/edit.html.twig b/templates/species/edit.html.twig new file mode 100644 index 0000000..8e4401b --- /dev/null +++ b/templates/species/edit.html.twig @@ -0,0 +1,13 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit Species{% endblock %} + +{% block body %} +

Edit Species

+ + {{ include('species/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('species/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/species/index.html.twig b/templates/species/index.html.twig index 666ad41..e855f6c 100644 --- a/templates/species/index.html.twig +++ b/templates/species/index.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% block title %}Herbarium - Espèces{% endblock %} +{% block title %}Species{% endblock %} {% block body %}
-

Liste des espèces

+

{{ 'list_of_species'|trans }}

{% for specie in species %} @@ -19,12 +19,12 @@
- 🔬 Nom Scientifique : {{ specie.scientificName }}
- 📍 Region : {{ specie.region }} + 🔬 {{ 'scientific_name'|trans }} : {{ specie.scientificName }}
+ 📍 {{ 'region'|trans }} : {{ specie.region }}

{% endfor %}
- Create new + {{ 'create_new'|trans }}
{% endblock %} diff --git a/templates/species/new.html.twig b/templates/species/new.html.twig index 8e58597..9b20266 100644 --- a/templates/species/new.html.twig +++ b/templates/species/new.html.twig @@ -3,9 +3,9 @@ {% block title %}New Species{% endblock %} {% block body %} -

Create new Species

+

{{ 'create_new_species'|trans }}

{{ include('species/_form.html.twig') }} - back to list + {{ 'back_to_list'|trans }} {% endblock %} diff --git a/templates/species/show.html.twig b/templates/species/show.html.twig index 152ea09..c3dd4e1 100644 --- a/templates/species/show.html.twig +++ b/templates/species/show.html.twig @@ -1,16 +1,16 @@ {% extends 'base.html.twig' %} -{% block title %}Species{% endblock %} +{% block title %}{{ 'species'|trans }}{% endblock %} {% block body %}

{{ species.vernacularName }}

- 🔬 Nom Scientifique : {{ species.scientificName }}
- 📍 Region : {{ species.region }} + 🔬 {{ 'scientific_name'|trans }} : {{ species.scientificName }}
+ 📍 {{ 'region'|trans }} : {{ species.region }}

-

Posts :

+

{{ 'posts'|trans }} :

{% for post in species.posts %}
@@ -18,18 +18,18 @@

{{ post.publicationDate | date }}

-
📍Géolocalisation
+
📍{{ 'geolocation'|trans }}
{{ post.longitude }} - {{ post.latitude }}
-
📍Commentaire
+
💬{{ 'commentary'|trans }}
{{ post.getCommentary }}
{% endfor %} - back to list + {{ 'back_to_list'|trans }} - edit + {{ 'edit'|trans }} {{ include('species/_delete_form.html.twig') }}
diff --git a/templates/species/table.html.twig b/templates/species/table.html.twig index 1303738..ba8c565 100644 --- a/templates/species/table.html.twig +++ b/templates/species/table.html.twig @@ -1,18 +1,18 @@ {% extends 'base.html.twig' %} -{% block title %}Species index{% endblock %} +{% block title %}{{ 'species_index'|trans }}{% endblock %} {% block body %} -

Species index

+

{{ 'species_index'|trans }}

- - - - + + + + @@ -23,17 +23,17 @@ {% else %} - + {% endfor %}
IdScientific_nameVernacular_nameRegionactions{{ 'scientific_name'|trans }}{{ 'vernacular_name'|trans }}{{ 'region'|trans }}Actions
{{ species.vernacularName }} {{ species.region }} - show - edit + {{ 'show'|trans }} + {{ 'edit'|trans }}
no records found{{ 'no_records_found'|trans }}
- Create new + {{ 'create_new'|trans }} {% endblock %} diff --git a/translations/.gitignore b/translations/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/translations/VerifyEmailBundle.en.xlf b/translations/VerifyEmailBundle.en.xlf new file mode 100644 index 0000000..0522bde --- /dev/null +++ b/translations/VerifyEmailBundle.en.xlf @@ -0,0 +1,42 @@ + + + +
+ +
+ + + %count% year|%count% years + %count% year|%count% years + + + %count% month|%count% months + %count% month|%count% months + + + %count% day|%count% days + %count% day|%count% days + + + %count% hour|%count% hours + %count% hour|%count% hours + + + %count% minute|%count% minutes + %count% minute|%count% minutes + + + The link to verify your email has expired. Please request a new link. + The link to verify your email has expired. Please request a new link. + + + The link to verify your email is invalid. Please request a new link. + The link to verify your email is invalid. Please request a new link. + + + The link to verify your email appears to be for a different account or email. Please request a new link. + The link to verify your email appears to be for a different account or email. Please request a new link. + + +
+
diff --git a/translations/VerifyEmailBundle.fr.xlf b/translations/VerifyEmailBundle.fr.xlf new file mode 100644 index 0000000..bfbb78c --- /dev/null +++ b/translations/VerifyEmailBundle.fr.xlf @@ -0,0 +1,42 @@ + + + +
+ +
+ + + %count% year|%count% years + %count% année|%count% années + + + %count% month|%count% months + %count% mois|%count% mois + + + %count% day|%count% days + %count% jour|%count% jours + + + %count% hour|%count% hours + %count% heure|%count% heures + + + %count% minute|%count% minutes + %count% minute|%count% minutes + + + The link to verify your email has expired. Please request a new link. + Le lien pour vérifier votre adresse e-mail a expiré. Veuillez refaire une demande de réinitialisation. + + + The link to verify your email is invalid. Please request a new link. + Le lien pour vérifier votre adresse e-mail est invalide. Veuillez refaire une demande de réinitialisation. + + + The link to verify your email appears to be for a different account or email. Please request a new link. + Le lien permettant de vérifier votre adresse e-mail semble correspondre à un autre compte ou e-mail. Veuillez refaire une demande de réinitialisation. + + +
+
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf new file mode 100644 index 0000000..587e1f6 --- /dev/null +++ b/translations/messages.en.xlf @@ -0,0 +1,166 @@ + + + +
+ +
+ + + delete + Delete + + + delete_confirm + Are you sure you want to delete ? + + + log_out + Log out + + + sign_in + Sign in + + + welcome_to_herbarium + Welcome to Herbarium + + + new_post + New post + + + create_new_post + Create new post + + + back_to_list + Back to list + + + post + Post + + + found_date + Found date + + + publication_date + Publication date + + + commentary + Commentary + + + edit + Edit + + + post_index + Post index + + + show + Show + + + no_records_found + No records found + + + create_new + Create new + + + register + Register + + + log_in + Log in + + + logout + Log out + + + password + Password + + + sign_up + Sign up + + + species + Species + + + list_of_species + List of species + + + scientific_name + Scientific name + + + region + Region + + + create_new_species + Create new species + + + posts + Posts + + + geolocation + Geolocation + + + species_index + Species index + + + vernacular_name + Vernacular name + + + delete_item_confirmation + Are you sure you want to delete this item ? + + + previous + Previous + + + next + Next + + + post_undefined + Post undefined + + + edit_species + Edit species + + + edit_post + Edit post + + + update + Update + + + save + Save + + +
+
diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf new file mode 100644 index 0000000..f518d69 --- /dev/null +++ b/translations/messages.fr.xlf @@ -0,0 +1,166 @@ + + + +
+ +
+ + + delete + Supprimer + + + delete_confirm + Êtes-vous sûr de vouloir supprimer ? + + + log_out + Se déconnecter + + + sign_in + Se connecter + + + welcome_to_herbarium + Bienvenue sur Herbarium + + + new_post + Nouvelle publication + + + create_new_post + Création d'une nouvelle publication + + + back_to_list + Retour à la liste + + + post + Publication + + + found_date + Date de découverte + + + publication_date + Date de publication + + + commentary + Commentaire + + + edit + Éditer + + + post_index + Index des publications + + + show + Montrer + + + no_records_found + Pas d'enregistrements trouvés + + + create_new + Création d'un nouvel enregistrement + + + register + Créer un compte + + + log_in + Se connecter + + + logout + Se déconnecter + + + password + Mot de passe + + + sign_up + S'inscrire + + + species + Espèces + + + list_of_species + Liste des espèces + + + scientific_name + Nom scientifique + + + region + Région + + + create_new_species + Créer une nouvelle espèce + + + posts + Publications + + + geolocation + Géolocalisation + + + species_index + Index des espèces + + + vernacular_name + Nom vernaculaire + + + delete_item_confirmation + Êtes-vous sûr de vouloir supprimer ? + + + previous + Retour + + + next + Suivant + + + post_undefined + Publication non définie + + + edit_species + Éditer l'espèce + + + edit_post + Éditer la publication + + + update + Mettre à jour + + + save + Sauvegarder + + +
+
diff --git a/translations/security.en.xlf b/translations/security.en.xlf new file mode 100644 index 0000000..cde7936 --- /dev/null +++ b/translations/security.en.xlf @@ -0,0 +1,82 @@ + + + +
+ +
+ + + An authentication exception occurred. + An authentication exception occurred. + + + Authentication credentials could not be found. + Authentication credentials could not be found. + + + Authentication request could not be processed due to a system problem. + Authentication request could not be processed due to a system problem. + + + Invalid credentials. + Invalid credentials. + + + Cookie has already been used by someone else. + Cookie has already been used by someone else. + + + Not privileged to request the resource. + Not privileged to request the resource. + + + Invalid CSRF token. + Invalid CSRF token. + + + No authentication provider found to support the authentication token. + No authentication provider found to support the authentication token. + + + No session available, it either timed out or cookies are not enabled. + No session available, it either timed out or cookies are not enabled. + + + No token could be found. + No token could be found. + + + Username could not be found. + Username could not be found. + + + Account has expired. + Account has expired. + + + Credentials have expired. + Credentials have expired. + + + Account is disabled. + Account is disabled. + + + Account is locked. + Account is locked. + + + Too many failed login attempts, please try again later. + Too many failed login attempts, please try again later. + + + Invalid or expired login link. + Invalid or expired login link. + + + Too many failed login attempts, please try again in %minutes% minute. + Too many failed login attempts, please try again in %minutes% minute. + + +
+
diff --git a/translations/security.fr.xlf b/translations/security.fr.xlf new file mode 100644 index 0000000..4735733 --- /dev/null +++ b/translations/security.fr.xlf @@ -0,0 +1,82 @@ + + + +
+ +
+ + + An authentication exception occurred. + Une exception d'authentification s'est produite. + + + Authentication credentials could not be found. + Les identifiants d'authentification n'ont pas pu être trouvés. + + + Authentication request could not be processed due to a system problem. + La requête d'authentification n'a pas pu être executée à cause d'un problème système. + + + Invalid credentials. + Identifiants invalides. + + + Cookie has already been used by someone else. + Le cookie a déjà été utilisé par quelqu'un d'autre. + + + Not privileged to request the resource. + Privilèges insuffisants pour accéder à la ressource. + + + Invalid CSRF token. + Jeton CSRF invalide. + + + No authentication provider found to support the authentication token. + Aucun fournisseur d'authentification n'a été trouvé pour supporter le jeton d'authentification. + + + No session available, it either timed out or cookies are not enabled. + Aucune session disponible, celle-ci a expiré ou les cookies ne sont pas activés. + + + No token could be found. + Aucun jeton n'a pu être trouvé. + + + Username could not be found. + Le nom d'utilisateur n'a pas pu être trouvé. + + + Account has expired. + Le compte a expiré. + + + Credentials have expired. + Les identifiants ont expiré. + + + Account is disabled. + Le compte est désactivé. + + + Account is locked. + Le compte est bloqué. + + + Too many failed login attempts, please try again later. + Plusieurs tentatives de connexion ont échoué, veuillez réessayer plus tard. + + + Invalid or expired login link. + Lien de connexion invalide ou expiré. + + + Too many failed login attempts, please try again in %minutes% minute. + Plusieurs tentatives de connexion ont échoué, veuillez réessayer dans %minutes% minute. + + +
+
diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf new file mode 100644 index 0000000..71cbc03 --- /dev/null +++ b/translations/validators.en.xlf @@ -0,0 +1,598 @@ + + + +
+ +
+ + + This value should be false. + This value should be false. + + + This value should be true. + This value should be true. + + + This value should be of type {{ type }}. + This value should be of type {{ type }}. + + + This value should be blank. + This value should be blank. + + + The value you selected is not a valid choice. + The value you selected is not a valid choice. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + + + One or more of the given values is invalid. + One or more of the given values is invalid. + + + This field was not expected. + This field was not expected. + + + This field is missing. + This field is missing. + + + This value is not a valid date. + This value is not a valid date. + + + This value is not a valid datetime. + This value is not a valid datetime. + + + This value is not a valid email address. + This value is not a valid email address. + + + The file could not be found. + The file could not be found. + + + The file is not readable. + The file is not readable. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + + + This value should be {{ limit }} or less. + This value should be {{ limit }} or less. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + + + This value should be {{ limit }} or more. + This value should be {{ limit }} or more. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + + + This value should not be blank. + This value should not be blank. + + + This value should not be null. + This value should not be null. + + + This value should be null. + This value should be null. + + + This value is not valid. + This value is not valid. + + + This value is not a valid time. + This value is not a valid time. + + + This value is not a valid URL. + This value is not a valid URL. + + + The two values should be equal. + The two values should be equal. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + + + The file is too large. + The file is too large. + + + The file could not be uploaded. + The file could not be uploaded. + + + This value should be a valid number. + This value should be a valid number. + + + This file is not a valid image. + This file is not a valid image. + + + This is not a valid IP address. + This value is not a valid IP address. + + + This value is not a valid language. + This value is not a valid language. + + + This value is not a valid locale. + This value is not a valid locale. + + + This value is not a valid country. + This value is not a valid country. + + + This value is already used. + This value is already used. + + + The size of the image could not be detected. + The size of the image could not be detected. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + + + This value should be the user's current password. + This value should be the user's current password. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + + + The file was only partially uploaded. + The file was only partially uploaded. + + + No file was uploaded. + No file was uploaded. + + + No temporary folder was configured in php.ini. + No temporary folder was configured in php.ini, or the configured folder does not exist. + + + Cannot write temporary file to disk. + Cannot write temporary file to disk. + + + A PHP extension caused the upload to fail. + A PHP extension caused the upload to fail. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + + + Invalid card number. + Invalid card number. + + + Unsupported card type or invalid card number. + Unsupported card type or invalid card number. + + + This is not a valid International Bank Account Number (IBAN). + This value is not a valid International Bank Account Number (IBAN). + + + This value is not a valid ISBN-10. + This value is not a valid ISBN-10. + + + This value is not a valid ISBN-13. + This value is not a valid ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + This value is neither a valid ISBN-10 nor a valid ISBN-13. + + + This value is not a valid ISSN. + This value is not a valid ISSN. + + + This value is not a valid currency. + This value is not a valid currency. + + + This value should be equal to {{ compared_value }}. + This value should be equal to {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + This value should be greater than {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + This value should be greater than or equal to {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + This value should be less than {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + This value should be less than or equal to {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + This value should not be equal to {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + + + An empty file is not allowed. + An empty file is not allowed. + + + The host could not be resolved. + The host could not be resolved. + + + This value does not match the expected {{ charset }} charset. + This value does not match the expected {{ charset }} charset. + + + This is not a valid Business Identifier Code (BIC). + This value is not a valid Business Identifier Code (BIC). + + + Error + Error + + + This is not a valid UUID. + This value is not a valid UUID. + + + This value should be a multiple of {{ compared_value }}. + This value should be a multiple of {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + + + This value should be valid JSON. + This value should be valid JSON. + + + This collection should contain only unique elements. + This collection should contain only unique elements. + + + This value should be positive. + This value should be positive. + + + This value should be either positive or zero. + This value should be either positive or zero. + + + This value should be negative. + This value should be negative. + + + This value should be either negative or zero. + This value should be either negative or zero. + + + This value is not a valid timezone. + This value is not a valid timezone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + This password has been leaked in a data breach, it must not be used. Please use another password. + + + This value should be between {{ min }} and {{ max }}. + This value should be between {{ min }} and {{ max }}. + + + This value is not a valid hostname. + This value is not a valid hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + The number of elements in this collection should be a multiple of {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + This value should satisfy at least one of the following constraints: + + + Each element of this collection should satisfy its own set of constraints. + Each element of this collection should satisfy its own set of constraints. + + + This value is not a valid International Securities Identification Number (ISIN). + This value is not a valid International Securities Identification Number (ISIN). + + + This value should be a valid expression. + This value should be a valid expression. + + + This value is not a valid CSS color. + This value is not a valid CSS color. + + + This value is not a valid CIDR notation. + This value is not a valid CIDR notation. + + + The value of the netmask should be between {{ min }} and {{ max }}. + The value of the netmask should be between {{ min }} and {{ max }}. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + + + The password strength is too low. Please use a stronger password. + The password strength is too low. Please use a stronger password. + + + This value contains characters that are not allowed by the current restriction-level. + This value contains characters that are not allowed by the current restriction-level. + + + Using invisible characters is not allowed. + Using invisible characters is not allowed. + + + Mixing numbers from different scripts is not allowed. + Mixing numbers from different scripts is not allowed. + + + Using hidden overlay characters is not allowed. + Using hidden overlay characters is not allowed. + + + The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}. + The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}. + + + The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}. + The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}. + + + This value is not a valid MAC address. + This value is not a valid MAC address. + + + This URL is missing a top-level domain. + This URL is missing a top-level domain. + + + This form should not contain extra fields. + This form should not contain extra fields. + + + The uploaded file was too large. Please try to upload a smaller file. + The uploaded file was too large. Please try to upload a smaller file. + + + The CSRF token is invalid. Please try to resubmit the form. + The CSRF token is invalid. Please try to resubmit the form. + + + This value is not a valid HTML5 color. + This value is not a valid HTML5 color. + + + Please enter a valid birthdate. + Please enter a valid birthdate. + + + The selected choice is invalid. + The selected choice is invalid. + + + The collection is invalid. + The collection is invalid. + + + Please select a valid color. + Please select a valid color. + + + Please select a valid country. + Please select a valid country. + + + Please select a valid currency. + Please select a valid currency. + + + Please choose a valid date interval. + Please choose a valid date interval. + + + Please enter a valid date and time. + Please enter a valid date and time. + + + Please enter a valid date. + Please enter a valid date. + + + Please select a valid file. + Please select a valid file. + + + The hidden field is invalid. + The hidden field is invalid. + + + Please enter an integer. + Please enter an integer. + + + Please select a valid language. + Please select a valid language. + + + Please select a valid locale. + Please select a valid locale. + + + Please enter a valid money amount. + Please enter a valid money amount. + + + Please enter a number. + Please enter a number. + + + The password is invalid. + The password is invalid. + + + Please enter a percentage value. + Please enter a percentage value. + + + The values do not match. + The values do not match. + + + Please enter a valid time. + Please enter a valid time. + + + Please select a valid timezone. + Please select a valid timezone. + + + Please enter a valid URL. + Please enter a valid URL. + + + Please enter a valid search term. + Please enter a valid search term. + + + Please provide a valid phone number. + Please provide a valid phone number. + + + The checkbox has an invalid value. + The checkbox has an invalid value. + + + Please enter a valid email address. + Please enter a valid email address. + + + Please select a valid option. + Please select a valid option. + + + Please select a valid range. + Please select a valid range. + + + Please enter a valid week. + Please enter a valid week. + + + There is already an account with this email + There is already an account with this email + + + You should agree to our terms. + You should agree to our terms. + + + Please enter a password + Please enter a password + + + Your password should be at least {{ limit }} characters + Your password should be at least {{ limit }} characters + + +
+
diff --git a/translations/validators.fr.xlf b/translations/validators.fr.xlf new file mode 100644 index 0000000..666570a --- /dev/null +++ b/translations/validators.fr.xlf @@ -0,0 +1,598 @@ + + + +
+ +
+ + + This value should be false. + Cette valeur doit être fausse. + + + This value should be true. + Cette valeur doit être vraie. + + + This value should be of type {{ type }}. + Cette valeur doit être de type {{ type }}. + + + This value should be blank. + Cette valeur doit être vide. + + + The value you selected is not a valid choice. + Cette valeur doit être l'un des choix proposés. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Vous devez sélectionner au moins {{ limit }} choix.|Vous devez sélectionner au moins {{ limit }} choix. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Vous devez sélectionner au maximum {{ limit }} choix.|Vous devez sélectionner au maximum {{ limit }} choix. + + + One or more of the given values is invalid. + Une ou plusieurs des valeurs soumises sont invalides. + + + This field was not expected. + Ce champ n'a pas été prévu. + + + This field is missing. + Ce champ est manquant. + + + This value is not a valid date. + Cette valeur n'est pas une date valide. + + + This value is not a valid datetime. + Cette valeur n'est pas une date valide. + + + This value is not a valid email address. + Cette valeur n'est pas une adresse email valide. + + + The file could not be found. + Le fichier n'a pas été trouvé. + + + The file is not readable. + Le fichier n'est pas lisible. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Le fichier est trop volumineux ({{ size }} {{ suffix }}). Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Le type du fichier est invalide ({{ type }}). Les types autorisés sont {{ types }}. + + + This value should be {{ limit }} or less. + Cette valeur doit être inférieure ou égale à {{ limit }}. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractère.|Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractères. + + + This value should be {{ limit }} or more. + Cette valeur doit être supérieure ou égale à {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractère.|Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractères. + + + This value should not be blank. + Cette valeur ne doit pas être vide. + + + This value should not be null. + Cette valeur ne doit pas être nulle. + + + This value should be null. + Cette valeur doit être nulle. + + + This value is not valid. + Cette valeur n'est pas valide. + + + This value is not a valid time. + Cette valeur n'est pas une heure valide. + + + This value is not a valid URL. + Cette valeur n'est pas une URL valide. + + + The two values should be equal. + Les deux valeurs doivent être identiques. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Le fichier est trop volumineux. Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}. + + + The file is too large. + Le fichier est trop volumineux. + + + The file could not be uploaded. + Le téléchargement de ce fichier est impossible. + + + This value should be a valid number. + Cette valeur doit être un nombre. + + + This file is not a valid image. + Ce fichier n'est pas une image valide. + + + This is not a valid IP address. + Cette valeur n'est pas une adresse IP valide. + + + This value is not a valid language. + Cette langue n'est pas valide. + + + This value is not a valid locale. + Ce paramètre régional n'est pas valide. + + + This value is not a valid country. + Ce pays n'est pas valide. + + + This value is already used. + Cette valeur est déjà utilisée. + + + The size of the image could not be detected. + La taille de l'image n'a pas pu être détectée. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + La largeur de l'image est trop grande ({{ width }}px). La largeur maximale autorisée est de {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + La largeur de l'image est trop petite ({{ width }}px). La largeur minimale attendue est de {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + La hauteur de l'image est trop grande ({{ height }}px). La hauteur maximale autorisée est de {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + La hauteur de l'image est trop petite ({{ height }}px). La hauteur minimale attendue est de {{ min_height }}px. + + + This value should be the user's current password. + Cette valeur doit être le mot de passe actuel de l'utilisateur. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Cette chaîne doit avoir exactement {{ limit }} caractère.|Cette chaîne doit avoir exactement {{ limit }} caractères. + + + The file was only partially uploaded. + Le fichier a été partiellement transféré. + + + No file was uploaded. + Aucun fichier n'a été transféré. + + + No temporary folder was configured in php.ini. + Aucun répertoire temporaire n'a été configuré dans le php.ini, ou le répertoire configuré n'existe pas. + + + Cannot write temporary file to disk. + Impossible d'écrire le fichier temporaire sur le disque. + + + A PHP extension caused the upload to fail. + Une extension PHP a empêché le transfert du fichier. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Cette collection doit contenir {{ limit }} élément ou plus.|Cette collection doit contenir {{ limit }} éléments ou plus. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Cette collection doit contenir {{ limit }} élément ou moins.|Cette collection doit contenir {{ limit }} éléments ou moins. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Cette collection doit contenir exactement {{ limit }} élément.|Cette collection doit contenir exactement {{ limit }} éléments. + + + Invalid card number. + Numéro de carte invalide. + + + Unsupported card type or invalid card number. + Type de carte non supporté ou numéro invalide. + + + This is not a valid International Bank Account Number (IBAN). + Cette valeur n'est pas un Numéro de Compte Bancaire International (IBAN) valide. + + + This value is not a valid ISBN-10. + Cette valeur n'est pas un code ISBN-10 valide. + + + This value is not a valid ISBN-13. + Cette valeur n'est pas un code ISBN-13 valide. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Cette valeur n'est ni un code ISBN-10, ni un code ISBN-13 valide. + + + This value is not a valid ISSN. + Cette valeur n'est pas un code ISSN valide. + + + This value is not a valid currency. + Cette valeur n'est pas une devise valide. + + + This value should be equal to {{ compared_value }}. + Cette valeur doit être égale à {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Cette valeur doit être supérieure à {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Cette valeur doit être supérieure ou égale à {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Cette valeur doit être identique à {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Cette valeur doit être inférieure à {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Cette valeur doit être inférieure ou égale à {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Cette valeur ne doit pas être égale à {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Cette valeur ne doit pas être identique à {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Le rapport largeur/hauteur de l'image est trop grand ({{ ratio }}). Le rapport maximal autorisé est {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Le rapport largeur/hauteur de l'image est trop petit ({{ ratio }}). Le rapport minimal attendu est {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + L'image est carrée ({{ width }}x{{ height }}px). Les images carrées ne sont pas autorisées. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + L'image est au format paysage ({{ width }}x{{ height }}px). Les images au format paysage ne sont pas autorisées. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + L'image est au format portrait ({{ width }}x{{ height }}px). Les images au format portrait ne sont pas autorisées. + + + An empty file is not allowed. + Un fichier vide n'est pas autorisé. + + + The host could not be resolved. + Le nom de domaine n'a pas pu être résolu. + + + This value does not match the expected {{ charset }} charset. + Cette valeur ne correspond pas au jeu de caractères {{ charset }} attendu. + + + This is not a valid Business Identifier Code (BIC). + Cette valeur n'est pas un Code Identifiant de Business (BIC) valide. + + + Error + Erreur + + + This is not a valid UUID. + Cette valeur n'est pas un UUID valide. + + + This value should be a multiple of {{ compared_value }}. + Cette valeur doit être un multiple de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ce code d'identification d'entreprise (BIC) n'est pas associé à l'IBAN {{ iban }}. + + + This value should be valid JSON. + Cette valeur doit être un JSON valide. + + + This collection should contain only unique elements. + Cette collection ne doit pas comporter de doublons. + + + This value should be positive. + Cette valeur doit être strictement positive. + + + This value should be either positive or zero. + Cette valeur doit être supérieure ou égale à zéro. + + + This value should be negative. + Cette valeur doit être strictement négative. + + + This value should be either negative or zero. + Cette valeur doit être inférieure ou égale à zéro. + + + This value is not a valid timezone. + Cette valeur n'est pas un fuseau horaire valide. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ce mot de passe a été divulgué lors d'une fuite de données, il ne doit plus être utilisé. Veuillez utiliser un autre mot de passe. + + + This value should be between {{ min }} and {{ max }}. + Cette valeur doit être comprise entre {{ min }} et {{ max }}. + + + This value is not a valid hostname. + Cette valeur n'est pas un nom d'hôte valide. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Le nombre d'éléments de cette collection doit être un multiple de {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Cette valeur doit satisfaire à au moins une des contraintes suivantes : + + + Each element of this collection should satisfy its own set of constraints. + Chaque élément de cette collection doit satisfaire à son propre jeu de contraintes. + + + This value is not a valid International Securities Identification Number (ISIN). + Cette valeur n'est pas un code international de sécurité valide (ISIN). + + + This value should be a valid expression. + Cette valeur doit être une expression valide. + + + This value is not a valid CSS color. + Cette valeur n'est pas une couleur CSS valide. + + + This value is not a valid CIDR notation. + Cette valeur n'est pas une notation CIDR valide. + + + The value of the netmask should be between {{ min }} and {{ max }}. + La valeur du masque de réseau doit être comprise entre {{ min }} et {{ max }}. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Le nom du fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractère.|Le nom de fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractères. + + + The password strength is too low. Please use a stronger password. + La robustesse du mot de passe est trop faible. Veuillez utiliser un mot de passe plus fort. + + + This value contains characters that are not allowed by the current restriction-level. + Cette valeur contient des caractères qui ne sont pas autorisés par le niveau de restriction actuel. + + + Using invisible characters is not allowed. + Utiliser des caractères invisibles n'est pas autorisé. + + + Mixing numbers from different scripts is not allowed. + Mélanger des chiffres provenant de différents scripts n'est pas autorisé. + + + Using hidden overlay characters is not allowed. + Utiliser des caractères de superposition cachés n'est pas autorisé. + + + The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}. + L'extension du fichier est invalide ({{ extension }}). Les extensions autorisées sont {{ extensions }}. + + + The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}. + L'encodage de caractères détecté est invalide ({{ detected }}). Les encodages autorisés sont {{ encodings }}. + + + This value is not a valid MAC address. + Cette valeur n'est pas une adresse MAC valide. + + + This URL is missing a top-level domain. + Cette URL doit contenir un domaine de premier niveau. + + + This form should not contain extra fields. + Ce formulaire ne doit pas contenir de champs supplémentaires. + + + The uploaded file was too large. Please try to upload a smaller file. + Le fichier téléchargé est trop volumineux. Merci d'essayer d'envoyer un fichier plus petit. + + + The CSRF token is invalid. Please try to resubmit the form. + Le jeton CSRF est invalide. Veuillez renvoyer le formulaire. + + + This value is not a valid HTML5 color. + Cette valeur n'est pas une couleur HTML5 valide. + + + Please enter a valid birthdate. + Veuillez entrer une date de naissance valide. + + + The selected choice is invalid. + Le choix sélectionné est invalide. + + + The collection is invalid. + La collection est invalide. + + + Please select a valid color. + Veuillez sélectionner une couleur valide. + + + Please select a valid country. + Veuillez sélectionner un pays valide. + + + Please select a valid currency. + Veuillez sélectionner une devise valide. + + + Please choose a valid date interval. + Veuillez choisir un intervalle de dates valide. + + + Please enter a valid date and time. + Veuillez saisir une date et une heure valides. + + + Please enter a valid date. + Veuillez entrer une date valide. + + + Please select a valid file. + Veuillez sélectionner un fichier valide. + + + The hidden field is invalid. + Le champ masqué n'est pas valide. + + + Please enter an integer. + Veuillez saisir un entier. + + + Please select a valid language. + Veuillez sélectionner une langue valide. + + + Please select a valid locale. + Veuillez sélectionner une langue valide. + + + Please enter a valid money amount. + Veuillez saisir un montant valide. + + + Please enter a number. + Veuillez saisir un nombre. + + + The password is invalid. + Le mot de passe est invalide. + + + Please enter a percentage value. + Veuillez saisir un pourcentage valide. + + + The values do not match. + Les valeurs ne correspondent pas. + + + Please enter a valid time. + Veuillez saisir une heure valide. + + + Please select a valid timezone. + Veuillez sélectionner un fuseau horaire valide. + + + Please enter a valid URL. + Veuillez saisir une URL valide. + + + Please enter a valid search term. + Veuillez saisir un terme de recherche valide. + + + Please provide a valid phone number. + Veuillez fournir un numéro de téléphone valide. + + + The checkbox has an invalid value. + La case à cocher a une valeur non valide. + + + Please enter a valid email address. + Veuillez saisir une adresse email valide. + + + Please select a valid option. + Veuillez sélectionner une option valide. + + + Please select a valid range. + Veuillez sélectionner une plage valide. + + + Please enter a valid week. + Veuillez entrer une semaine valide. + + + There is already an account with this email + Il existe déjà un compte avec cet email + + + You should agree to our terms. + Vous devez accepter nos conditions + + + Please enter a password + Veuillez entrer un mot de passe + + + Your password should be at least {{ limit }} characters + Votre mot de passe doit comporter au moins {{ limite }} caractères + + +
+
-- 2.43.0 From bc6951d0a59575931563e99b7fbb0a5b7e582431 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Thu, 13 Jun 2024 09:12:03 +0200 Subject: [PATCH 22/29] Remove hardcoded values --- templates/post/index.html.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/post/index.html.twig b/templates/post/index.html.twig index 5ea65a2..d14adbc 100644 --- a/templates/post/index.html.twig +++ b/templates/post/index.html.twig @@ -4,16 +4,16 @@ {% block body %} {% for post in posts.iterator %} -
+
-
{{ post.species ? post.species.vernacularName : 'post_undefined'|trans }}
+
{{ post.species ? post.species.vernacularName : 'post_undefined'|trans }}
{{ post.foundDate | date("d/m/Y \\à H \\h") }}

{{ post.latitude }}, {{ post.longitude }}, {{ post.altitude }}m

{{ post.commentary }}

{% endfor %} -- 2.43.0 From 0beb0cd95f6a6d41da53c79303dd160f9ac1d3b4 Mon Sep 17 00:00:00 2001 From: Bastien OLLIER Date: Thu, 13 Jun 2024 09:30:25 +0200 Subject: [PATCH 23/29] Update interface (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: bastien ollier Reviewed-on: https://codefirst.iut.uca.fr/git/clement.freville2/herbarium/pulls/17 Reviewed-by: Clément FRÉVILLE Co-authored-by: Bastien OLLIER Co-committed-by: Bastien OLLIER --- src/Controller/PostController.php | 17 +++++++--------- src/Controller/SpeciesController.php | 2 +- templates/base.html.twig | 23 +++++++++++++++++++++- templates/post/edit.html.twig | 2 +- templates/post/new.html.twig | 2 +- templates/post/show.html.twig | 4 ++-- templates/post/table.html.twig | 2 +- templates/species/_form.html.twig | 2 +- templates/species/edit.html.twig | 2 +- templates/species/new.html.twig | 2 +- templates/species/show.html.twig | 4 ++-- tests/Controller/PostControllerTest.php | 6 +++--- tests/Controller/SpeciesControllerTest.php | 2 +- translations/messages.fr.xlf | 2 +- 14 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/Controller/PostController.php b/src/Controller/PostController.php index af3d395..7f4e77f 100644 --- a/src/Controller/PostController.php +++ b/src/Controller/PostController.php @@ -34,16 +34,13 @@ class PostController extends AbstractController ]); } - #[Route('/post', name: 'app_post_index', methods: ['GET'])] + #[Route('/posts', name: 'app_post_index', methods: ['GET'])] public function table(PostRepository $repository): Response { - $posts = $repository->findAll(); - return $this->render('post/table.html.twig', [ - 'posts' => $posts, - ]); + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); } - #[Route('/post/new', name: 'app_post_new', methods: ['GET', 'POST'])] + #[Route('/posts/new', name: 'app_post_new', methods: ['GET', 'POST'])] #[IsGranted('ROLE_USER')] public function new(Request $request, EntityManagerInterface $entityManager): Response { @@ -64,7 +61,7 @@ class PostController extends AbstractController ]); } - #[Route('/post/{id}', name: 'app_post_show', methods: ['GET'])] + #[Route('/posts/{id}', name: 'app_post_show', methods: ['GET'])] public function show(Post $post): Response { $form = $this->createForm(CommentType::class, new Comment(), [ @@ -76,7 +73,7 @@ class PostController extends AbstractController ]); } - #[Route('/post/{id}/edit', name: 'app_post_edit', methods: ['GET', 'POST'])] + #[Route('/posts/{id}/edit', name: 'app_post_edit', methods: ['GET', 'POST'])] #[IsGranted('ROLE_USER')] public function edit(Request $request, Post $post, EntityManagerInterface $entityManager): Response { @@ -95,7 +92,7 @@ class PostController extends AbstractController ]); } - #[Route('/post/{id}', name: 'app_post_delete', methods: ['POST'])] + #[Route('/posts/{id}', name: 'app_post_delete', methods: ['POST'])] #[IsGranted('ROLE_USER')] public function delete(Request $request, Post $post, EntityManagerInterface $entityManager): Response { @@ -107,7 +104,7 @@ class PostController extends AbstractController return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); } - #[Route('/post/{id}/comment', name: 'app_post_comment', methods: ['POST'])] + #[Route('/posts/{id}/comment', name: 'app_post_comment', methods: ['POST'])] public function publishComment(Request $request, Post $post, EntityManagerInterface $entityManager, #[CurrentUser] User $user): Response { $comment = new Comment(); diff --git a/src/Controller/SpeciesController.php b/src/Controller/SpeciesController.php index a495a0f..4af751c 100644 --- a/src/Controller/SpeciesController.php +++ b/src/Controller/SpeciesController.php @@ -18,7 +18,7 @@ class SpeciesController extends AbstractController #[Route('/', name: 'app_species_index', methods: ['GET'])] public function table(SpeciesRepository $speciesRepository): Response { - return $this->render('species/table.html.twig', [ + return $this->render('species/index.html.twig', [ 'species' => $speciesRepository->findAll(), ]); } diff --git a/templates/base.html.twig b/templates/base.html.twig index 4f29e24..0ced7a9 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -15,7 +15,28 @@
{% endfor %} {% include '_pagination.html.twig' %} {% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} -- 2.43.0 From ba82630d8a42760c437c3d26024bcdc8479a0ea3 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 14 Jun 2024 09:42:09 +0200 Subject: [PATCH 26/29] Disable service implementation --- config/services.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index f07d469..8919b4f 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -30,7 +30,3 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones - -when@prod: - services: - App\Service\ImageSafetyServiceInterface: '@App\Service\SightEngineImageSafetyService' -- 2.43.0 From 4f19bac7078530d5535c0b1b66d714c3eba78be0 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 14 Jun 2024 12:18:58 +0200 Subject: [PATCH 27/29] Fix upload base URL behind a subdirectory --- templates/post/show.html.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/post/show.html.twig b/templates/post/show.html.twig index e990e12..c79e591 100644 --- a/templates/post/show.html.twig +++ b/templates/post/show.html.twig @@ -34,7 +34,8 @@ {% if post.image %} Image - + {# Vich doesn't prefix the path, as asset() would. #} + {% endif %} -- 2.43.0 From 71ddec420c7226323fc0879a4d0ffe5212eb4eff Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 14 Jun 2024 12:51:03 +0200 Subject: [PATCH 28/29] Tweak style --- public/css/app.css | 12 ++++++++++++ templates/_pagination.html.twig | 2 +- templates/base.html.twig | 6 +++--- templates/post/index.html.twig | 12 +++++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index 5da752c..c62a3cc 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -10,3 +10,15 @@ .no-style:focus { outline: none; } + +.grid-4 { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +@media (max-width: 992px) { + .grid-4 { + grid-template-columns: 1fr; + } +} diff --git a/templates/_pagination.html.twig b/templates/_pagination.html.twig index 9c9fdae..c3f6baa 100644 --- a/templates/_pagination.html.twig +++ b/templates/_pagination.html.twig @@ -1,6 +1,6 @@ {% set route = app.request.attributes.get('_route') %}