diff --git a/composer.json b/composer.json index be710c098861ab6f6bb5a2d296adbe70b9a8bcfe..e4be74580969f9fe5ede545fba740a770ba5cbae 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,8 @@ "symfony/web-link": "7.2.*", "symfony/yaml": "7.2.*", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/twig": "^2.12|^3.0", + "vich/uploader-bundle": "^2.5" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index f404a87bef3d6fa7b8d72ad76da2f6a599982164..c0551765396703af12a8c1a52cce026ea8978de9 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": "eaa614d3c00b5654c4cc228dcad4b8c8", + "content-hash": "b2ee6e5d461f24017c9b451bcf06e3f1", "packages": [ { "name": "composer/semver", @@ -1368,6 +1368,70 @@ ], "time": "2024-12-27T00:36:43+00:00" }, + { + "name": "jms/metadata", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0|^2.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.8.0" + }, + "time": "2023-02-15T13:44:18+00:00" + }, { "name": "monolog/monolog", "version": "3.8.1", @@ -7501,6 +7565,114 @@ ], "time": "2025-02-13T08:34:43+00:00" }, + { + "name": "vich/uploader-bundle", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/dustin10/VichUploaderBundle.git", + "reference": "79fd69ad8f32d4a33ac8783d7720e16e78bb8098" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/79fd69ad8f32d4a33ac8783d7720e16e78bb8098", + "reference": "79fd69ad8f32d4a33ac8783d7720e16e78bb8098", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3.0", + "ext-simplexml": "*", + "jms/metadata": "^2.4", + "php": "^8.1", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher-contracts": "^3.1", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "symfony/property-access": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "league/flysystem": "<2.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.8", + "doctrine/doctrine-bundle": "^2.7", + "doctrine/mongodb-odm": "^2.4", + "doctrine/orm": "^2.13", + "ext-sqlite3": "*", + "knplabs/knp-gaufrette-bundle": "dev-master", + "league/flysystem-bundle": "^2.4 || ^3.0", + "league/flysystem-memory": "^2.0 || ^3.0", + "matthiasnoback/symfony-dependency-injection-test": "^5.1", + "mikey179/vfsstream": "^1.6.11", + "phpunit/phpunit": "^9.6", + "symfony/asset": "^5.4 || ^6.0 || ^7.0", + "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^7.0", + "symfony/security-csrf": "^5.4 || ^6.0 || ^7.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "For integration with Doctrine", + "doctrine/mongodb-odm-bundle": "For integration with Doctrine ODM", + "doctrine/orm": "For integration with Doctrine ORM", + "doctrine/phpcr-odm": "For integration with Doctrine PHPCR", + "knplabs/knp-gaufrette-bundle": "For integration with Gaufrette", + "league/flysystem-bundle": "For integration with Flysystem", + "liip/imagine-bundle": "To generate image thumbnails", + "oneup/flysystem-bundle": "For integration with Flysystem", + "symfony/asset": "To generate better links", + "symfony/form": "To handle uploads in forms", + "symfony/yaml": "To use YAML mapping" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Vich\\UploaderBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dustin Dobervich", + "email": "ddobervich@gmail.com" + } + ], + "description": "Ease file uploads attached to entities", + "homepage": "https://github.com/dustin10/VichUploaderBundle", + "keywords": [ + "file uploads", + "upload" + ], + "support": { + "issues": "https://github.com/dustin10/VichUploaderBundle/issues", + "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.5.1" + }, + "time": "2024-12-31T08:59:03+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/bundles.php b/config/bundles.php index 4e3a56077fb806de5799fe7e82171145e3ce4ff5..0dcb24b223b0bb7faebe09146bb9dcece4b5f2ad 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -12,5 +12,6 @@ return [ Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], ]; diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1a636c10eaa7167374e8306f92c3bd4ba6cd0c2a --- /dev/null +++ b/config/packages/vich_uploader.yaml @@ -0,0 +1,7 @@ +vich_uploader: + db_driver: orm + mappings: + item_images: + uri_prefix: /uploads/images + upload_destination: '%kernel.project_dir%/public/uploads/images' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 2d6a76f94dce138741e2d63ae83a11c1879031d9..8a31e80a6bc3977dacf465cdf30e022ebadfaefd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,6 +4,7 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + uploads_directory: '%kernel.project_dir%/public/uploads' services: # default configuration for services in *this* file diff --git a/migrations/Version20250319154617.php b/migrations/Version20250319154617.php new file mode 100644 index 0000000000000000000000000000000000000000..923e42fd5420faf7c6a781e383207fb1ee361499 --- /dev/null +++ b/migrations/Version20250319154617.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250319154617 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250319232441.php b/migrations/Version20250319232441.php new file mode 100644 index 0000000000000000000000000000000000000000..802975756a27c75253e43cd3f39368936869719c --- /dev/null +++ b/migrations/Version20250319232441.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250319232441 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250319235558.php b/migrations/Version20250319235558.php new file mode 100644 index 0000000000000000000000000000000000000000..ff7671133646ebe1e042d319dfcdb0c633c43571 --- /dev/null +++ b/migrations/Version20250319235558.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250319235558 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof ADD item_id INT NOT NULL'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2F32C3F7126F525E ON purchase_proof (item_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP INDEX UNIQ_2F32C3F7126F525E ON purchase_proof'); + $this->addSql('ALTER TABLE purchase_proof DROP item_id'); + } +} diff --git a/migrations/Version20250319235641.php b/migrations/Version20250319235641.php new file mode 100644 index 0000000000000000000000000000000000000000..328a77c4acca0bce1beca90f0f3b9be3a4d80fbd --- /dev/null +++ b/migrations/Version20250319235641.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250319235641 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + $this->addSql('ALTER TABLE purchase_proof ADD item_id INT NOT NULL'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2F32C3F7126F525E ON purchase_proof (item_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP INDEX UNIQ_2F32C3F7126F525E ON purchase_proof'); + $this->addSql('ALTER TABLE purchase_proof DROP item_id'); + } +} diff --git a/migrations/Version20250320000820.php b/migrations/Version20250320000820.php new file mode 100644 index 0000000000000000000000000000000000000000..d8e5490b066dbc664be55e7e326348e25e7d18d1 --- /dev/null +++ b/migrations/Version20250320000820.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320000820 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE purchase_proof'); + } +} diff --git a/migrations/Version20250320000903.php b/migrations/Version20250320000903.php new file mode 100644 index 0000000000000000000000000000000000000000..c4b987f4d4d26d4120261fc70fd0289939d0cc6f --- /dev/null +++ b/migrations/Version20250320000903.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320000903 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE purchase_proof'); + } +} diff --git a/migrations/Version20250320001018.php b/migrations/Version20250320001018.php new file mode 100644 index 0000000000000000000000000000000000000000..e292a9b894835671238be06c2cdad9b69ce68df3 --- /dev/null +++ b/migrations/Version20250320001018.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320001018 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, purchase_proof_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, UNIQUE INDEX UNIQ_1F1B251EFECA7547 (purchase_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id)'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250320001428.php b/migrations/Version20250320001428.php new file mode 100644 index 0000000000000000000000000000000000000000..cba08978371af6bd9710b73c55e77135aa597486 --- /dev/null +++ b/migrations/Version20250320001428.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320001428 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('DROP INDEX UNIQ_1F1B251EFECA7547 ON item'); + $this->addSql('ALTER TABLE item DROP purchase_proof_id'); + $this->addSql('ALTER TABLE purchase_proof ADD item_id INT NOT NULL'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2F32C3F7126F525E ON purchase_proof (item_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item ADD purchase_proof_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id) ON UPDATE NO ACTION ON DELETE NO ACTION'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1F1B251EFECA7547 ON item (purchase_proof_id)'); + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP INDEX UNIQ_2F32C3F7126F525E ON purchase_proof'); + $this->addSql('ALTER TABLE purchase_proof DROP item_id'); + } +} diff --git a/migrations/Version20250320001458.php b/migrations/Version20250320001458.php new file mode 100644 index 0000000000000000000000000000000000000000..345d1e8fff4725016aa11fe221db9d4b238d557b --- /dev/null +++ b/migrations/Version20250320001458.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320001458 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250320001931.php b/migrations/Version20250320001931.php new file mode 100644 index 0000000000000000000000000000000000000000..acff107589c2946865e9553b323abe1c9c92a2f0 --- /dev/null +++ b/migrations/Version20250320001931.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320001931 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251EFECA7547'); + $this->addSql('DROP INDEX UNIQ_1F1B251EFECA7547 ON item'); + $this->addSql('ALTER TABLE item DROP purchase_proof_id'); + $this->addSql('ALTER TABLE purchase_proof ADD item_id INT NOT NULL'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2F32C3F7126F525E ON purchase_proof (item_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE item ADD purchase_proof_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE item ADD CONSTRAINT FK_1F1B251EFECA7547 FOREIGN KEY (purchase_proof_id) REFERENCES purchase_proof (id) ON UPDATE NO ACTION ON DELETE NO ACTION'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1F1B251EFECA7547 ON item (purchase_proof_id)'); + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP INDEX UNIQ_2F32C3F7126F525E ON purchase_proof'); + $this->addSql('ALTER TABLE purchase_proof DROP item_id'); + } +} diff --git a/migrations/Version20250320001951.php b/migrations/Version20250320001951.php new file mode 100644 index 0000000000000000000000000000000000000000..97040f22cc3d7365c65735023a7aff1bf11a0792 --- /dev/null +++ b/migrations/Version20250320001951.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320001951 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250320002407.php b/migrations/Version20250320002407.php new file mode 100644 index 0000000000000000000000000000000000000000..57bc4aedc90dc687ee23f0b3a9331494c5fca01a --- /dev/null +++ b/migrations/Version20250320002407.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20250320002407 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE item (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, price DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE purchase_proof (id INT AUTO_INCREMENT NOT NULL, item_id INT NOT NULL, congrats_text VARCHAR(255) NOT NULL, image_path VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2F32C3F7126F525E (item_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE purchase_proof ADD CONSTRAINT FK_2F32C3F7126F525E FOREIGN KEY (item_id) REFERENCES item (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE purchase_proof DROP FOREIGN KEY FK_2F32C3F7126F525E'); + $this->addSql('DROP TABLE item'); + $this->addSql('DROP TABLE purchase_proof'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..5ecfcbfbf8436273985bd1961d6ec24addfefc86 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,169 @@ +* { + box-sizing: border-box; + font-family: Helvetica, Arial, sans-serif; +} + +body { + margin: 0; + background: #f4f4f4; +} + +header { + display: flex; + align-items: center; + background: #00B8DE; + color: white; + padding: 10px; + gap: 15px; +} + +.user-icon { + width: 35px; + height: 35px; + background: #ccc; + border-radius: 50%; +} + +h1 { + margin: 0; +} + +h1 a { + color: white; + text-decoration: none; +} + +.search-bar { + margin-left: auto; + padding: 5px; + border-radius: 5px; + border: none; +} + +.container { + display: flex; + justify-content: space-around; + align-items: flex-start; + margin: 20px; +} + +.info-text { + color: red; + font-size: 14px; + max-width: 150px; +} + +.form-section { + background: white; + padding: 20px; + border: 2px solid #99CC33; + border-radius: 10px; + width: 300px; +} + +.input-box label { + display: block; + font-weight: bold; + margin-top: 10px; +} + +.input-box input, +.input-box textarea { + width: 100%; + padding: 8px; + border: 1px solid #00B8DE; + border-radius: 5px; +} + +.image-section { + background: white; + padding: 20px; + border: 2px solid #99CC33; + border-radius: 10px; + text-align: center; +} + +.image-label { + font-weight: bold; + display: block; + margin-bottom: 10px; +} + +.image-upload-box { + border: 2px dashed #00B8DE; + padding: 40px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; +} +/* Add these styles to your existing CSS file */ + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + height: 100%; +} + +.item-section { + display: flex; + gap: 20px; + justify-content: center; + align-items: center; + width: 100%; +} + +.article-box, .upload-box { + border: 2px solid #99CC33; + padding: 20px; + background: white; + width: 200px; + text-align: center; + position: relative; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.upload-form { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; +} + +.form-control { + padding: 8px; + border: 1px solid #99CC33; + border-radius: 5px; + outline: none; + width: 100%; +} + +.submit-button { + padding: 10px; + background-color: #00B8DE; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + width: 100%; +} + +.submit-button:hover { + background-color: #0099B8; +} + diff --git a/public/uploads/images/image-67daec93d2de5553979377.jpg b/public/uploads/images/image-67daec93d2de5553979377.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67daec93d2de5553979377.jpg differ diff --git a/public/uploads/images/image-67daee56bb05a205705852.jpg b/public/uploads/images/image-67daee56bb05a205705852.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67daee56bb05a205705852.jpg differ diff --git a/public/uploads/images/image-67db4b30ea363264558781.jpg b/public/uploads/images/image-67db4b30ea363264558781.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85b79202e906b754db44422ca371be61a633867b Binary files /dev/null and b/public/uploads/images/image-67db4b30ea363264558781.jpg differ diff --git a/public/uploads/images/screenshot-from-2025-02-14-09-20-02-67db63d057533675522937.png b/public/uploads/images/screenshot-from-2025-02-14-09-20-02-67db63d057533675522937.png new file mode 100644 index 0000000000000000000000000000000000000000..142942288002c6363e8e11582cefeea2a3d1cd92 Binary files /dev/null and b/public/uploads/images/screenshot-from-2025-02-14-09-20-02-67db63d057533675522937.png differ diff --git a/src/Controller/ItemController.php b/src/Controller/ItemController.php new file mode 100644 index 0000000000000000000000000000000000000000..365fbc0baee599846533aec362f6f07868e7aa69 --- /dev/null +++ b/src/Controller/ItemController.php @@ -0,0 +1,81 @@ +<?php + +namespace App\Controller; + +use App\Entity\Item; +use App\Form\ItemType; +use App\Repository\ItemRepository; +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; + +#[Route('/item')] +final class ItemController extends AbstractController +{ + #[Route(name: 'app_item_index', methods: ['GET'])] + public function index(ItemRepository $itemRepository): Response + { + return $this->render('item/index.html.twig', [ + 'items' => $itemRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_item_new', methods: ['GET', 'POST'])] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $item = new Item(); + $form = $this->createForm(ItemType::class, $item); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($item); + $entityManager->flush(); + + return $this->redirectToRoute('app_item_index'); + } + + return $this->render('item/new.html.twig', [ + 'item' => $item, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_item_show', methods: ['GET'])] + public function show(Item $item): Response + { + return $this->render('item/show.html.twig', [ + 'item' => $item, + ]); + } + + #[Route('/{id}/edit', name: 'app_item_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, Item $item, EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(ItemType::class, $item); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_item_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('item/edit.html.twig', [ + 'item' => $item, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_item_delete', methods: ['POST'])] + public function delete(Request $request, Item $item, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$item->getId(), $request->getPayload()->getString('_token'))) { + $entityManager->remove($item); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_item_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/src/Controller/PurchaseProofController.php b/src/Controller/PurchaseProofController.php new file mode 100644 index 0000000000000000000000000000000000000000..b31218ca477fe2796c7a6f2ed10ec3a68d090783 --- /dev/null +++ b/src/Controller/PurchaseProofController.php @@ -0,0 +1,50 @@ +<?php + +namespace App\Controller; + +use App\Entity\Item; +use App\Entity\PurchaseProof; +use App\Form\PurchaseProofType; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +#[Route('/item/{id}/purchaseproof')] +class PurchaseProofController extends AbstractController +{ + #[Route('/new', name: 'purchaseproof_new', methods: ['GET', 'POST'])] + public function new(Item $item, Request $request, EntityManagerInterface $entityManager): Response + { + if ($item->getPurchaseProof()) { + $this->addFlash('warning', 'This item already has a purchase proof.'); + return $this->redirectToRoute('app_item_show', ['id' => $item->getId()]); + } + + $purchaseProof = new PurchaseProof(); + $purchaseProof->setItem($item); + + $form = $this->createForm(PurchaseProofType::class, $purchaseProof); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $imageFile = $form->get('imagePath')->getData(); + if ($imageFile) { + $newFilename = uniqid().'.'.$imageFile->guessExtension(); + $imageFile->move($this->getParameter('uploads_directory'), $newFilename); + $purchaseProof->setImagePath($newFilename); + } + + $entityManager->persist($purchaseProof); + $entityManager->flush(); + + return $this->redirectToRoute('app_item_show', ['id' => $item->getId()]); + } + + return $this->render('purchase_proof/new.html.twig', [ + 'form' => $form->createView(), + 'item' => $item, + ]); + } +} diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 2db4fc660557e63c4b2b6627d9280208d1a62ed4..7b6dba68030b825aa0c309bc95bfdd3eb5914bae 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -4,8 +4,12 @@ namespace App\Entity; use App\Repository\ItemRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\Validator\Constraints as Assert; +use Vich\UploaderBundle\Mapping\Annotation as Vich; #[ORM\Entity(repositoryClass: ItemRepository::class)] +#[Vich\Uploadable] class Item { #[ORM\Id] @@ -22,13 +26,20 @@ class Item #[ORM\Column(length: 255)] private ?string $url = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255, nullable: true)] private ?string $image = null; + #[Vich\UploadableField(mapping: 'item_images', fileNameProperty: 'image')] + #[Assert\Image( + mimeTypes: ["image/jpeg", "image/png"], + maxSize: "5M" + )] + private ?File $imageFile = null; + #[ORM\Column] private ?float $price = null; - #[ORM\OneToOne(cascade: ['persist', 'remove'])] + #[ORM\OneToOne(mappedBy: 'item', cascade: ['persist', 'remove'])] private ?PurchaseProof $purchaseProof = null; public function getId(): ?int @@ -44,7 +55,6 @@ class Item public function setTitle(string $title): static { $this->title = $title; - return $this; } @@ -56,7 +66,6 @@ class Item public function setDescription(string $description): static { $this->description = $description; - return $this; } @@ -68,7 +77,6 @@ class Item public function setUrl(string $url): static { $this->url = $url; - return $this; } @@ -77,13 +85,22 @@ class Item return $this->image; } - public function setImage(string $image): static + public function setImage(?string $image): self { $this->image = $image; - return $this; } + public function getImageFile(): ?File + { + return $this->imageFile; + } + + public function setImageFile(?File $imageFile): void + { + $this->imageFile = $imageFile; + } + public function getPrice(): ?float { return $this->price; @@ -92,7 +109,6 @@ class Item public function setPrice(float $price): static { $this->price = $price; - return $this; } @@ -103,8 +119,11 @@ class Item public function setPurchaseProof(?PurchaseProof $purchaseProof): static { + // Ensure the relationship is bidirectional + if ($purchaseProof !== null && $purchaseProof->getItem() !== $this) { + $purchaseProof->setItem($this); + } $this->purchaseProof = $purchaseProof; - return $this; } -} +} \ No newline at end of file diff --git a/src/Entity/PurchaseProof.php b/src/Entity/PurchaseProof.php index 1315adad75e51a5c393f33c7ecee0021c7b56440..cbaf858b9f0fc716eb976dea7fb9dce980cb3e3b 100644 --- a/src/Entity/PurchaseProof.php +++ b/src/Entity/PurchaseProof.php @@ -19,6 +19,13 @@ class PurchaseProof #[ORM\Column(length: 255)] private ?string $imagePath = null; + #[ORM\OneToOne(inversedBy: "purchaseProof", cascade: ["persist", "remove"])] + #[ORM\JoinColumn(nullable: false)] + private ?Item $item = null; + + #[ORM\Column] + private ?int $item_id = null; // Explicit item_id column + public function getId(): ?int { return $this->id; @@ -32,7 +39,6 @@ class PurchaseProof public function setCongratsText(string $congratsText): static { $this->congratsText = $congratsText; - return $this; } @@ -44,7 +50,29 @@ class PurchaseProof public function setImagePath(string $imagePath): static { $this->imagePath = $imagePath; + return $this; + } + + public function getItem(): ?Item + { + return $this->item; + } + public function setItem(Item $item): static + { + $this->item = $item; + $this->item_id = $item->getId(); // Keep item_id in sync + return $this; + } + + public function getItemId(): ?int + { + return $this->item_id; + } + + public function setItemId(int $item_id): static + { + $this->item_id = $item_id; return $this; } -} +} \ No newline at end of file diff --git a/src/Form/ItemType.php b/src/Form/ItemType.php new file mode 100644 index 0000000000000000000000000000000000000000..a54aba6180dd3f6bf3a09aed6a85a28295d9a0ea --- /dev/null +++ b/src/Form/ItemType.php @@ -0,0 +1,40 @@ +<?php + +namespace App\Form; + +use App\Entity\Item; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Vich\UploaderBundle\Form\Type\VichImageType; + +class ItemType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('title', TextType::class, ['label' => 'Article name']) + ->add('url', TextType::class, ['label' => 'Buy URL']) + ->add('price', NumberType::class, ['label' => 'Price']) + ->add('description', TextType::class, ['label' => 'Description']) + ->add('imageFile', VichImageType::class, [ + 'label' => 'Upload a new image', // ✅ Change le label pour éviter la confusion + 'required' => false, // ✅ Permet de ne pas forcer un nouvel upload + 'allow_delete' => false, // ✅ Désactive la suppression automatique + 'download_uri' => false, // ✅ Désactive le lien de téléchargement + 'image_uri' => false, // ✅ Empêche l'affichage de l'image dans l'input + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Item::class, + 'csrf_protection' => true, // ✅ Active la protection CSRF + 'csrf_field_name' => '_token', // ✅ Définit le nom du champ CSRF + 'csrf_token_id' => 'submit', // ✅ Identifie le token + ]); + } +} diff --git a/src/Form/PurchaseProofType.php b/src/Form/PurchaseProofType.php new file mode 100644 index 0000000000000000000000000000000000000000..bc22634d4ae3d520aa9f7ae0a5b24ea1c81bf26a --- /dev/null +++ b/src/Form/PurchaseProofType.php @@ -0,0 +1,45 @@ +<?php + +namespace App\Form; + +use App\Entity\PurchaseProof; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\File; + +class PurchaseProofType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('congratsText', TextType::class, [ + 'label' => 'Congratulations Message' + ]) + ->add('imagePath', FileType::class, [ + 'label' => 'Upload Image', + 'mapped' => false, + 'required' => false, + 'constraints' => [ + new File([ + 'maxSize' => '5M', + 'mimeTypes' => ['image/jpeg', 'image/png', 'image/webp'], + 'mimeTypesMessage' => 'Please upload a valid image file', + ]) + ] + ]) + ->add('save', SubmitType::class, [ + 'label' => 'Save Purchase Proof' + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => PurchaseProof::class, + ]); + } +} diff --git a/symfony.lock b/symfony.lock index 494742435e71508edaa6b77a2067a9728d7cd36e..2101fd274bcf20f927f2d6a13578218b0a1d6f5e 100644 --- a/symfony.lock +++ b/symfony.lock @@ -298,5 +298,14 @@ }, "twig/extra-bundle": { "version": "v3.20.0" + }, + "vich/uploader-bundle": { + "version": "2.5", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.13", + "ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36" + } } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 3cda30fb023bd2dcf148bf08b8658ec8ba1a332a..2eac7d55cf86cc2a94f66f2aaddedac60af3edff 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -4,6 +4,7 @@ <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> + <link rel="stylesheet" href="{{ asset('css/style.css') }}"> {% block stylesheets %} {% endblock %} diff --git a/templates/item/_delete_form.html.twig b/templates/item/_delete_form.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..5793a05562d579ec720fbf5476046f5f38894409 --- /dev/null +++ b/templates/item/_delete_form.html.twig @@ -0,0 +1,4 @@ +<form method="post" action="{{ path('app_item_delete', {'id': item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');"> + <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ item.id) }}"> + <button class="btn">Delete</button> +</form> diff --git a/templates/item/_form.html.twig b/templates/item/_form.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..bf20b98fb01ed38c82b670ff3fe5d7e207e80f16 --- /dev/null +++ b/templates/item/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + <button class="btn">{{ button_label|default('Save') }}</button> +{{ form_end(form) }} diff --git a/templates/item/edit.html.twig b/templates/item/edit.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e10931fa28225b74a7a071d459edf64ad649b118 --- /dev/null +++ b/templates/item/edit.html.twig @@ -0,0 +1,44 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ item is defined ? 'Edit Item' : 'New Item' }}{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="{{ path('app_item_index') }}">Item title</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + {{ form_start(form, {'attr': {'class': 'input-box'}}) }} + + {{ form_row(form.title) }} + {{ form_row(form.url) }} + {{ form_row(form.price) }} + {{ form_row(form.description) }} + + <!-- ✅ Affichage unique de l'image actuelle --> + {% if item is defined and item.image %} + <h3>Current Image</h3> + <div class="image-container"> + <img src="{{ asset('uploads/images/' ~ item.image) }}" + alt="Item Image" + style="max-width: 200px; border-radius: 5px; display: block; margin-bottom: 10px;"> + </div> + {% endif %} + + <!-- ✅ Champ d'upload sans affichage d'image --> + <h3>Upload a new image</h3> + <label for="image-upload" class="upload-label">Choose a file</label> + {{ form_widget(form.imageFile, {'attr': {'class': 'file-input', 'id': 'image-upload'}}) }} + + <button class="btn">{{ item is defined ? 'Update' : 'Confirm' }}</button> + + {# ✅ Empêche Symfony d'afficher les champs restants automatiquement #} + {{ form_end(form) }} + </section> + </div> + </main> +{% endblock %} diff --git a/templates/item/index.html.twig b/templates/item/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..ea9218712fb00085a6958ab9bb79fc155c247748 --- /dev/null +++ b/templates/item/index.html.twig @@ -0,0 +1,57 @@ +{% extends 'base.html.twig' %} + +{% block title %}Item index{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="#">Item Management</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + <h1>Item index</h1> + + <table class="table"> + <thead> + <tr> + <th>Id</th> + <th>Title</th> + <th>Description</th> + <th>Url</th> + <th>Image</th> + <th>Price</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for item in items %} + <tr> + <td>{{ item.id }}</td> + <td>{{ item.title }}</td> + <td>{{ item.description }}</td> + <td>{{ item.url }}</td> + <td>{{ item.image }}</td> + <td>{{ item.price }}</td> + <td> + <a href="{{ path('app_item_show', {'id': item.id}) }}">Show</a> + <a href="{{ path('app_item_edit', {'id': item.id}) }}">Edit</a> + </td> + </tr> + {% else %} + <tr> + <td colspan="7">No records found</td> + </tr> + {% endfor %} + </tbody> + </table> + + <a href="{{ path('app_item_new') }}" class="btn">Create new</a> + </section> + </div> + </main> + + +{% endblock %} diff --git a/templates/item/new.html.twig b/templates/item/new.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..9773d03919bc8ece5a13a2a4c4505890726c226e --- /dev/null +++ b/templates/item/new.html.twig @@ -0,0 +1,30 @@ +{% extends 'base.html.twig' %} + +{% block title %}New Item{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="{{ path('app_item_index') }}">Item title</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + {{ form_start(form, {'attr': {'class': 'input-box'}}) }} + {{ form_row(form.title) }} + {{ form_row(form.url) }} + {{ form_row(form.price) }} + {{ form_row(form.description) }} + + <!-- Upload d’image --> + <h3>Item image</h3> + {{ form_row(form.imageFile) }} + + <button class="btn">Confirm</button> + {{ form_end(form) }} + </section> + </div> + </main> +{% endblock %} diff --git a/templates/item/show.html.twig b/templates/item/show.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e7c5f66933b3779c025fe9f91a0b324b0cd5d73d --- /dev/null +++ b/templates/item/show.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block title %}Item{% endblock %} + +{% block body %} + <h1>Item</h1> + + <table class="table"> + <tbody> + <tr> + <th>Id</th> + <td>{{ item.id }}</td> + </tr> + <tr> + <th>Title</th> + <td>{{ item.title }}</td> + </tr> + <tr> + <th>Description</th> + <td>{{ item.description }}</td> + </tr> + <tr> + <th>Url</th> + <td>{{ item.url }}</td> + </tr> + <tr> + <th>Image</th> + <td>{{ item.image }}</td> + </tr> + <tr> + <th>Price</th> + <td>{{ item.price }}</td> + </tr> + </tbody> + </table> + + <a href="{{ path('app_item_index') }}">back to list</a> + + <a href="{{ path('app_item_edit', {'id': item.id}) }}">edit</a> + + {{ include('item/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/purchase_proof/index.html.twig b/templates/purchase_proof/index.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..78a3a987919b26ade9149cb4a5d0d62182495d6b --- /dev/null +++ b/templates/purchase_proof/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello PurchaseProofController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/firas/Desktop/Wishlist-application/src/Controller/PurchaseProofController.php</code></li> + <li>Your template at <code>/home/firas/Desktop/Wishlist-application/templates/purchase_proof/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/purchase_proof/new.html.twig b/templates/purchase_proof/new.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..d56226013632ab9926e4b3452034caf099ec197d --- /dev/null +++ b/templates/purchase_proof/new.html.twig @@ -0,0 +1,40 @@ +{% extends 'base.html.twig' %} + +{% block title %}Add Purchase Proof{% endblock %} + +{% block body %} +<header> + <div class="user-icon"></div> + <h1><a href="#">Purchase proof</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + <div class="menu-icon"></div> +</header> + +<main> + <div class="container"> + <h2>Add Purchase Proof for {{ item.title }}</h2> + <section class="item-section"> + <div class="article-box"> + <p class="label">{{ item.title }}</p> + <div class="image-box"> + <img src="{{ asset('uploads/' ~ item.image) }}" alt="Item photo"> + </div> + </div> + + <div class="upload-box"> + {{ form_start(form, {'attr': {'enctype': 'multipart/form-data', 'class': 'upload-form'}}) }} + <div class="form-group"> + {{ form_label(form.congratsText) }} + {{ form_widget(form.congratsText, {'attr': {'class': 'form-control', 'placeholder': 'Write a message…'}}) }} + </div> + <div class="form-group"> + {{ form_label(form.imagePath) }} + {{ form_widget(form.imagePath, {'attr': {'class': 'form-control'}}) }} + </div> + <button type="submit" class="submit-button">Submit</button> + {{ form_end(form) }} + </div> + </section> + </div> +</main> +{% endblock %} \ No newline at end of file diff --git a/tests/Controller/PurchaseProofControllerTest.php b/tests/Controller/PurchaseProofControllerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..17136da85de3671d50c78896385e3180597a469b --- /dev/null +++ b/tests/Controller/PurchaseProofControllerTest.php @@ -0,0 +1,16 @@ +<?php + +namespace App\Tests\Controller; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +final class PurchaseProofControllerTest extends WebTestCase +{ + public function testIndex(): void + { + $client = static::createClient(); + $client->request('GET', '/purchase/proof'); + + self::assertResponseIsSuccessful(); + } +}