diff --git a/README.md b/README.md index 362f2960e4d28895dde5c1c00ddbf88f7ef11f76..a6a08a1cd2f29b18fecba2ba8731681e8a9c34be 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ ## ИнÑÑ‚Ñ€ÑƒÐºÑ†Ð¸Ñ +## Воркер отправки Email квеÑтов + +ЗапуÑтить команду - `bin/console messenger:consume -v scheduler_quests` + ## Kafka <details> <summary>ИнÑтрукциÑ</summary> diff --git a/app/composer.json b/app/composer.json index 804133de31d0d2376b8fa6c92f68a5c9d260c9b2..7020df0f0724267b600d0f8707996b6bc825d63a 100644 --- a/app/composer.json +++ b/app/composer.json @@ -14,6 +14,8 @@ "doctrine/orm": "^3.2", "lexik/jwt-authentication-bundle": "^3.0", "nelmio/api-doc-bundle": "^4.27", + "nelmio/cors-bundle": "^2.5", + "predis/predis": "^2.2", "symfony/asset": "7.0.*", "symfony/cache": "7.0.*", "symfony/console": "7.0.*", @@ -25,6 +27,7 @@ "symfony/messenger": "7.0.*", "symfony/mime": "7.0.*", "symfony/runtime": "7.0.*", + "symfony/scheduler": "7.0.*", "symfony/security-bundle": "7.0.*", "symfony/serializer": "7.0.*", "symfony/twig-bundle": "7.0.*", diff --git a/app/composer.lock b/app/composer.lock index df4798038f84eca925af6362f3e88f58e2452ed3..e212146e5c8118a70b540ce4c75637b9f37bbc8a 100644 --- a/app/composer.lock +++ b/app/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": "a47e42ecd6c25174dcfe545065022162", + "content-hash": "54d204742929222e6a66061365bec4eb", "packages": [ { "name": "doctrine/cache", @@ -1664,6 +1664,68 @@ }, "time": "2024-06-12T23:47:19+00:00" }, + { + "name": "nelmio/cors-bundle", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "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.5.0" + }, + "time": "2024-06-24T21:25:28+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -1886,6 +1948,67 @@ }, "time": "2024-05-31T08:52:43+00:00" }, + { + "name": "predis/predis", + "version": "v2.2.2", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ~9.4.4" + }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "description": "A flexible and feature-complete Redis client for PHP.", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v2.2.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2023-09-13T16:42:03+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -4816,6 +4939,86 @@ ], "time": "2024-04-18T09:29:19+00:00" }, + { + "name": "symfony/scheduler", + "version": "v7.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/scheduler.git", + "reference": "91a0c028f2183b111e92e32061bb9db9a9599133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/scheduler/zipball/91a0c028f2183b111e92e32061bb9db9a9599133", + "reference": "91a0c028f2183b111e92e32061bb9db9a9599133", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0" + }, + "require-dev": { + "dragonmantank/cron-expression": "^3.1", + "symfony/cache": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Scheduler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sergey Rabochiy", + "email": "upyx.00@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides scheduling through Symfony Messenger", + "homepage": "https://symfony.com", + "keywords": [ + "cron", + "schedule", + "scheduler" + ], + "support": { + "source": "https://github.com/symfony/scheduler/tree/v7.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-02T15:49:03+00:00" + }, { "name": "symfony/security-bundle", "version": "v7.0.8", diff --git a/app/config/bundles.php b/app/config/bundles.php index 386a29b1da7b000598112d980c463d18ef32bcfe..facf965f4101e598e0514bd9d9c077d78f6c72a2 100644 --- a/app/config/bundles.php +++ b/app/config/bundles.php @@ -10,4 +10,5 @@ return [ Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], ]; diff --git a/app/config/packages/cache.yaml b/app/config/packages/cache.yaml index 6899b72003fca67f5a56b945cd3e07f5c8a33774..ddb1ec38c4474dab9b4fd6ad4111c5e952ea859c 100644 --- a/app/config/packages/cache.yaml +++ b/app/config/packages/cache.yaml @@ -8,8 +8,10 @@ framework: # Other options include: # Redis - #app: cache.adapter.redis - #default_redis_provider: redis://localhost + default_redis_provider: 'redis://redis' + pools: + custom_cache_pool: + adapter: cache.adapter.redis # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) #app: cache.adapter.apcu diff --git a/app/config/packages/doctrine.yaml b/app/config/packages/doctrine.yaml index d42c52d6d2573bc94f3423d8f6d63a8eac3b61d8..3c43cba7220ae959751ad5bf27a8bb62db46053c 100644 --- a/app/config/packages/doctrine.yaml +++ b/app/config/packages/doctrine.yaml @@ -22,6 +22,9 @@ doctrine: dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App + result_cache_driver: + type: pool + pool: custom_cache_pool when@test: doctrine: diff --git a/app/config/packages/messenger.yaml b/app/config/packages/messenger.yaml index 6dc7da12750522c0d51bb45032c65c70d8f51bca..bf8155995e5d4207c7a728ce63f471eda6bbb02d 100644 --- a/app/config/packages/messenger.yaml +++ b/app/config/packages/messenger.yaml @@ -4,6 +4,8 @@ framework: # failure_transport: failed transports: + sync: 'sync://' + send_transport: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: @@ -26,6 +28,7 @@ framework: routing: 'App\Messenger\Message\SendMessage': send_transport + 'App\Messenger\Message\QuestMessage': send_transport # Route your messages to the transports # 'App\Message\YourMessage': async diff --git a/app/config/packages/nelmio_cors.yaml b/app/config/packages/nelmio_cors.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fa676773376376ea28d1a6bdceddbad0a4236b11 --- /dev/null +++ b/app/config/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'POST'] + allow_headers: ['*'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/api/': ~ diff --git a/app/config/services.yaml b/app/config/services.yaml index 02884eb17e3bae4741b0165e463a6f60db3e9f74..39dc3d24820c87fd5339aad0f967809e5dc4456f 100644 --- a/app/config/services.yaml +++ b/app/config/services.yaml @@ -36,6 +36,14 @@ services: $confirmType: '%confirm_type%' $fromEmail: '%from_email%' + App\Messenger\Handler\QuestEndMessageHandler: + arguments: + $fromEmail: '%from_email%' + + App\Messenger\Handler\QuestStartMessageHandler: + arguments: + $fromEmail: '%from_email%' + App\Listeners\KernelExceptionListener: tags: - { name: kernel.event_listener, event: kernel.exception } diff --git a/app/migrations/Version20240624095415.php b/app/migrations/Version20240624095415.php new file mode 100644 index 0000000000000000000000000000000000000000..030c3a231e62cb8b604c0a8340afc7e9d86132b1 --- /dev/null +++ b/app/migrations/Version20240624095415.php @@ -0,0 +1,81 @@ +<?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 Version20240624095415 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 SEQUENCE appointment_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE favorite_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE "like_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE quest_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE quest_image_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE review_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE appointment (id INT NOT NULL, related_user_id INT NOT NULL, quest_id INT DEFAULT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_FE38F84498771930 ON appointment (related_user_id)'); + $this->addSql('CREATE INDEX IDX_FE38F844209E9EF4 ON appointment (quest_id)'); + $this->addSql('CREATE TABLE favorite (id INT NOT NULL, related_user_id INT NOT NULL, quest_id INT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_68C58ED998771930 ON favorite (related_user_id)'); + $this->addSql('CREATE INDEX IDX_68C58ED9209E9EF4 ON favorite (quest_id)'); + $this->addSql('CREATE TABLE "like" (id INT NOT NULL, review_id INT NOT NULL, related_user_id INT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_AC6340B33E2E969B ON "like" (review_id)'); + $this->addSql('CREATE INDEX IDX_AC6340B398771930 ON "like" (related_user_id)'); + $this->addSql('CREATE TABLE quest (id INT NOT NULL, name VARCHAR(255) NOT NULL, short_description TEXT NOT NULL, full_description TEXT DEFAULT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, final_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, max_appointments INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE quest_image (id INT NOT NULL, quest_id INT DEFAULT NULL, path VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_96809DC3209E9EF4 ON quest_image (quest_id)'); + $this->addSql('CREATE TABLE review (id INT NOT NULL, related_user_id INT NOT NULL, quest_id INT NOT NULL, text TEXT DEFAULT NULL, rating INT NOT NULL, create_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, update_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_794381C698771930 ON review (related_user_id)'); + $this->addSql('CREATE INDEX IDX_794381C6209E9EF4 ON review (quest_id)'); + $this->addSql('ALTER TABLE appointment ADD CONSTRAINT FK_FE38F84498771930 FOREIGN KEY (related_user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE appointment ADD CONSTRAINT FK_FE38F844209E9EF4 FOREIGN KEY (quest_id) REFERENCES quest (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE favorite ADD CONSTRAINT FK_68C58ED998771930 FOREIGN KEY (related_user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE favorite ADD CONSTRAINT FK_68C58ED9209E9EF4 FOREIGN KEY (quest_id) REFERENCES quest (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "like" ADD CONSTRAINT FK_AC6340B33E2E969B FOREIGN KEY (review_id) REFERENCES review (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "like" ADD CONSTRAINT FK_AC6340B398771930 FOREIGN KEY (related_user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE quest_image ADD CONSTRAINT FK_96809DC3209E9EF4 FOREIGN KEY (quest_id) REFERENCES quest (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C698771930 FOREIGN KEY (related_user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C6209E9EF4 FOREIGN KEY (quest_id) REFERENCES quest (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE appointment_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE favorite_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE "like_id_seq" CASCADE'); + $this->addSql('DROP SEQUENCE quest_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE quest_image_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE review_id_seq CASCADE'); + $this->addSql('ALTER TABLE appointment DROP CONSTRAINT FK_FE38F84498771930'); + $this->addSql('ALTER TABLE appointment DROP CONSTRAINT FK_FE38F844209E9EF4'); + $this->addSql('ALTER TABLE favorite DROP CONSTRAINT FK_68C58ED998771930'); + $this->addSql('ALTER TABLE favorite DROP CONSTRAINT FK_68C58ED9209E9EF4'); + $this->addSql('ALTER TABLE "like" DROP CONSTRAINT FK_AC6340B33E2E969B'); + $this->addSql('ALTER TABLE "like" DROP CONSTRAINT FK_AC6340B398771930'); + $this->addSql('ALTER TABLE quest_image DROP CONSTRAINT FK_96809DC3209E9EF4'); + $this->addSql('ALTER TABLE review DROP CONSTRAINT FK_794381C698771930'); + $this->addSql('ALTER TABLE review DROP CONSTRAINT FK_794381C6209E9EF4'); + $this->addSql('DROP TABLE appointment'); + $this->addSql('DROP TABLE favorite'); + $this->addSql('DROP TABLE "like"'); + $this->addSql('DROP TABLE quest'); + $this->addSql('DROP TABLE quest_image'); + $this->addSql('DROP TABLE review'); + } +} diff --git a/app/migrations/Version20240624101121.php b/app/migrations/Version20240624101121.php new file mode 100644 index 0000000000000000000000000000000000000000..08ca8e64327bc0140176c448a72786caca340abb --- /dev/null +++ b/app/migrations/Version20240624101121.php @@ -0,0 +1,62 @@ +<?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 Version20240624101121 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 SEQUENCE genre_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE tag_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE theme_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE genre (id INT NOT NULL, name VARCHAR(255) NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE tag (id INT NOT NULL, name VARCHAR(255) NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE tag_quest (tag_id INT NOT NULL, quest_id INT NOT NULL, PRIMARY KEY(tag_id, quest_id))'); + $this->addSql('CREATE INDEX IDX_61FBE111BAD26311 ON tag_quest (tag_id)'); + $this->addSql('CREATE INDEX IDX_61FBE111209E9EF4 ON tag_quest (quest_id)'); + $this->addSql('CREATE TABLE theme (id INT NOT NULL, name VARCHAR(255) NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE tag_quest ADD CONSTRAINT FK_61FBE111BAD26311 FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE tag_quest ADD CONSTRAINT FK_61FBE111209E9EF4 FOREIGN KEY (quest_id) REFERENCES quest (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE quest ADD theme_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE quest ADD genre_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE quest ADD CONSTRAINT FK_4317F81759027487 FOREIGN KEY (theme_id) REFERENCES theme (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE quest ADD CONSTRAINT FK_4317F8174296D31F FOREIGN KEY (genre_id) REFERENCES genre (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_4317F81759027487 ON quest (theme_id)'); + $this->addSql('CREATE INDEX IDX_4317F8174296D31F ON quest (genre_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE quest DROP CONSTRAINT FK_4317F8174296D31F'); + $this->addSql('ALTER TABLE quest DROP CONSTRAINT FK_4317F81759027487'); + $this->addSql('DROP SEQUENCE genre_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE tag_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE theme_id_seq CASCADE'); + $this->addSql('ALTER TABLE tag_quest DROP CONSTRAINT FK_61FBE111BAD26311'); + $this->addSql('ALTER TABLE tag_quest DROP CONSTRAINT FK_61FBE111209E9EF4'); + $this->addSql('DROP TABLE genre'); + $this->addSql('DROP TABLE tag'); + $this->addSql('DROP TABLE tag_quest'); + $this->addSql('DROP TABLE theme'); + $this->addSql('DROP INDEX IDX_4317F81759027487'); + $this->addSql('DROP INDEX IDX_4317F8174296D31F'); + $this->addSql('ALTER TABLE quest DROP theme_id'); + $this->addSql('ALTER TABLE quest DROP genre_id'); + } +} diff --git a/app/src/Controller/ProfileController.php b/app/src/Controller/ProfileController.php index 2f9448814b83803201199e561c3edde09a81374d..8386604cb5b28c55808e4d07f42585ad46d885b6 100644 --- a/app/src/Controller/ProfileController.php +++ b/app/src/Controller/ProfileController.php @@ -7,8 +7,11 @@ use App\Service\Dto\Classes\ChangeProfileDto; use App\Service\Dto\Classes\ImageDto; use App\Service\Dto\Classes\RecoveryCodeDto; use App\Service\Dto\Classes\RecoveryDto; +use App\Service\Response\Classes\FavoritesResponse; use App\Service\Response\Classes\ProfileResponse; +use App\Service\Response\Classes\QuestsResponse; use App\Service\Response\Classes\Response; +use App\Service\Response\Classes\ReviewsResponse; use Nelmio\ApiDocBundle\Annotation\Model; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -36,6 +39,54 @@ class ProfileController extends AbstractController return $actionService->getResponse(); } + #[Route('/profile/favorites', name: 'favorites', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: FavoritesResponse::class, groups: ["message", "data", "card"]) + ) + )] + public function favorites( + #[Autowire(service: 'action.favorites')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/profile/reviews', name: 'reviews', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: ReviewsResponse::class, groups: ["message", "data", "card"]) + ) + )] + public function reviews( + #[Autowire(service: 'action.reviews')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/profile/quests', name: 'profile_quests', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: QuestsResponse::class, groups: ["message", "data", "card"]) + ) + )] + public function quests( + #[Autowire(service: 'action.profile.quests')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + #[Route('/profile/delete', name: 'profile_delete', methods: ['GET'])] #[OA\Response( response: 200, diff --git a/app/src/Controller/QuestController.php b/app/src/Controller/QuestController.php new file mode 100644 index 0000000000000000000000000000000000000000..08593cb3150f88eef19ebf6d1778033a0b690615 --- /dev/null +++ b/app/src/Controller/QuestController.php @@ -0,0 +1,247 @@ +<?php + +namespace App\Controller; + +use App\Entity\Quest; +use App\Service\Action\ActionServiceInterface; +use App\Service\Action\Classes\CreateReview; +use App\Service\Dto\Classes\CreateReviewDto; +use App\Service\Dto\Classes\FilterDto; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\Classes\RegisterCodeDto; +use App\Service\Dto\Classes\UpdateReviewDto; +use App\Service\Response\Classes\FilterParamsResponse; +use App\Service\Response\Classes\FilterResponse; +use App\Service\Response\Classes\QuestResponse; +use App\Service\Response\Classes\QuestsResponse; +use App\Service\Response\Classes\Response; +use Nelmio\ApiDocBundle\Annotation\Model; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Routing\Attribute\Route; +use OpenApi\Attributes as OA; + +#[Route('/api', name: 'api_')] +#[OA\Tag(name: 'КвеÑÑ‚Ñ‹')] +class QuestController extends AbstractController +{ + #[Route('/quests', name: 'quests', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: QuestsResponse::class, groups: ["message", "data", "card"]) + ) + )] + public function quests( + #[Autowire(service: 'action.quests')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest', name: 'quest', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: QuestResponse::class, groups: ["message", "data", "detail"]) + ) + )] + public function quest( + #[Autowire(service: 'action.quest')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/filter/set', name: 'filter_set', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: FilterDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: QuestsResponse::class, groups: ["message", "data", "card"]) + ) + )] + public function setFilter( + #[Autowire(service: 'action.filter.set')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/filter/get', name: 'filter_get', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: FilterResponse::class, groups: ["message", "data"]) + ) + )] + public function getFilter( + #[Autowire(service: 'action.filter.get')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/filter/params', name: 'filter_params', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: FilterParamsResponse::class, groups: ["message", "data", "filter"]) + ) + )] + public function getFilterParams( + #[Autowire(service: 'action.filter.params')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/subscribe', name: 'quest_subscribe', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function subscribe( + #[Autowire(service: 'action.quest.subscribe')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/unsubscribe', name: 'quest_unsubscribe', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function unsubscribe( + #[Autowire(service: 'action.quest.unsubscribe')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/review/create', name: 'quest_review_create', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: CreateReviewDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function createReview( + #[Autowire(service: 'action.quest.review.create')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/review/update', name: 'quest_review_update', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: UpdateReviewDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function updateReview( + #[Autowire(service: 'action.quest.review.update')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/review/delete', name: 'quest_review_delete', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function deleteReview( + #[Autowire(service: 'action.quest.review.delete')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/review/like', name: 'quest_review_like', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function likeReview( + #[Autowire(service: 'action.quest.review.like')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } + + #[Route('/quest/review/unlike', name: 'quest_review_unlike', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: IdDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] + public function unlikeReview( + #[Autowire(service: 'action.quest.review.unlike')] + ActionServiceInterface $actionService + ): JsonResponse + { + return $actionService->getResponse(); + } +} \ No newline at end of file diff --git a/app/src/Entity/Appointment.php b/app/src/Entity/Appointment.php new file mode 100644 index 0000000000000000000000000000000000000000..77eaeefb3b7279ab0462c92314af52f577257df8 --- /dev/null +++ b/app/src/Entity/Appointment.php @@ -0,0 +1,70 @@ +<?php + +namespace App\Entity; + +use App\Repository\AppointmentRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: AppointmentRepository::class)] +class Appointment +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'appointments')] + #[ORM\JoinColumn(nullable: false)] + private ?User $related_user = null; + + #[ORM\ManyToOne(inversedBy: 'appointments')] + private ?Quest $quest = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getRelatedUser(): ?User + { + return $this->related_user; + } + + public function setRelatedUser(?User $related_user): static + { + $this->related_user = $related_user; + + return $this; + } + + #[Groups(['all', 'profile'])] + public function getQuest(): ?Quest + { + return $this->quest; + } + + public function setQuest(?Quest $quest): static + { + $this->quest = $quest; + + return $this; + } + + #[Groups(['all', 'profile'])] + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } +} diff --git a/app/src/Entity/Favorite.php b/app/src/Entity/Favorite.php new file mode 100644 index 0000000000000000000000000000000000000000..16d9e4de58ed268375f125d0e1d41ccf46c4691a --- /dev/null +++ b/app/src/Entity/Favorite.php @@ -0,0 +1,71 @@ +<?php + +namespace App\Entity; + +use App\Repository\FavoriteRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: FavoriteRepository::class)] +class Favorite +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'favorites')] + #[ORM\JoinColumn(nullable: false)] + private ?User $related_user = null; + + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: false)] + private ?Quest $quest = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getRelatedUser(): ?User + { + return $this->related_user; + } + + public function setRelatedUser(?User $related_user): static + { + $this->related_user = $related_user; + + return $this; + } + + #[Groups(['all', 'card'])] + public function getQuest(): ?Quest + { + return $this->quest; + } + + public function setQuest(?Quest $quest): static + { + $this->quest = $quest; + + return $this; + } + + #[Groups(['all', 'card'])] + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } +} diff --git a/app/src/Entity/Genre.php b/app/src/Entity/Genre.php new file mode 100644 index 0000000000000000000000000000000000000000..efc906337330fd5a2b2b470944f4e5e8e615b33f --- /dev/null +++ b/app/src/Entity/Genre.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Entity; + +use App\Repository\GenreRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: GenreRepository::class)] +class Genre +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + public function getId(): ?int + { + return $this->id; + } + + #[Groups(['all', 'card', 'detail', 'filter'])] + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } +} diff --git a/app/src/Entity/Like.php b/app/src/Entity/Like.php new file mode 100644 index 0000000000000000000000000000000000000000..ec6a2be82b71925b2e246f77c41f9ce3a517564b --- /dev/null +++ b/app/src/Entity/Like.php @@ -0,0 +1,72 @@ +<?php + +namespace App\Entity; + +use App\Repository\LikeRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: LikeRepository::class)] +#[ORM\Table(name: '`like`')] +class Like +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'likes')] + #[ORM\JoinColumn(nullable: false)] + private ?Review $review = null; + + #[ORM\ManyToOne(inversedBy: 'likes')] + #[ORM\JoinColumn(nullable: false)] + private ?User $related_user = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getReview(): ?Review + { + return $this->review; + } + + public function setReview(?Review $review): static + { + $this->review = $review; + + return $this; + } + + #[Groups(['all', 'detail', 'review', 'card'])] + public function getRelatedUser(): ?User + { + return $this->related_user; + } + + public function setRelatedUser(?User $related_user): static + { + $this->related_user = $related_user; + + return $this; + } + + #[Groups(['all', 'detail', 'review', 'card'])] + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } +} diff --git a/app/src/Entity/Quest.php b/app/src/Entity/Quest.php new file mode 100644 index 0000000000000000000000000000000000000000..cbe969605c980fc88756f6c0c42b0e65c497158c --- /dev/null +++ b/app/src/Entity/Quest.php @@ -0,0 +1,328 @@ +<?php + +namespace App\Entity; + +use App\Repository\QuestRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: QuestRepository::class)] +class Quest +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[ORM\Column(type: Types::TEXT)] + private ?string $short_description = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $full_description = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $final_date = null; + + #[ORM\Column(nullable: true)] + private ?int $max_appointments = null; + + /** + * @var Collection<int, QuestImage> + */ + #[ORM\OneToMany(targetEntity: QuestImage::class, mappedBy: 'quest')] + private Collection $gallery; + + /** + * @var Collection<int, Appointment> + */ + #[ORM\OneToMany(targetEntity: Appointment::class, mappedBy: 'quest')] + private Collection $appointments; + + /** + * @var Collection<int, Review> + */ + #[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'quest')] + private Collection $reviews; + + /** + * @var Collection<int, Tag> + */ + #[ORM\ManyToMany(targetEntity: Tag::class, mappedBy: 'quests')] + private Collection $tags; + + #[ORM\ManyToOne] + private ?Theme $theme = null; + + #[ORM\ManyToOne] + private ?Genre $genre = null; + + public function __construct() + { + $this->gallery = new ArrayCollection(); + $this->appointments = new ArrayCollection(); + $this->reviews = new ArrayCollection(); + $this->tags = new ArrayCollection(); + } + + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getId(): ?int + { + return $this->id; + } + + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + #[Groups(['all', 'card', 'profile'])] + public function getShortDescription(): ?string + { + return $this->short_description; + } + + public function setShortDescription(string $short_description): static + { + $this->short_description = $short_description; + + return $this; + } + + #[Groups(['all', 'detail'])] + public function getFullDescription(): ?string + { + return $this->full_description; + } + + public function setFullDescription(?string $full_description): static + { + $this->full_description = $full_description; + + return $this; + } + + #[Groups(['all', 'card', 'profile'])] + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } + + #[Groups(['all', 'detail'])] + public function getFinalDate(): ?\DateTimeInterface + { + return $this->final_date; + } + + public function setFinalDate(\DateTimeInterface $final_date): static + { + $this->final_date = $final_date; + + return $this; + } + + #[Groups(['all', 'detail'])] + public function getMaxAppointments(): ?int + { + return $this->max_appointments; + } + + public function setMaxAppointments(?int $max_appointments): static + { + $this->max_appointments = $max_appointments; + + return $this; + } + + /** + * @return Collection<int, QuestImage> + */ + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getGallery(): Collection + { + return $this->gallery; + } + + public function addGallery(QuestImage $gallery): static + { + if (!$this->gallery->contains($gallery)) { + $this->gallery->add($gallery); + $gallery->setQuest($this); + } + + return $this; + } + + public function removeGallery(QuestImage $gallery): static + { + if ($this->gallery->removeElement($gallery)) { + // set the owning side to null (unless already changed) + if ($gallery->getQuest() === $this) { + $gallery->setQuest(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Appointment> + */ + #[Groups(['all'])] + public function getAppointments(): Collection + { + return $this->appointments; + } + + public function addAppointment(Appointment $appointment): static + { + if (!$this->appointments->contains($appointment)) { + $this->appointments->add($appointment); + $appointment->setQuest($this); + } + + return $this; + } + + #[Groups(['all', 'detail'])] + public function getAppointmentCount(): int + { + return count($this->appointments); + } + + public function removeAppointment(Appointment $appointment): static + { + if ($this->appointments->removeElement($appointment)) { + // set the owning side to null (unless already changed) + if ($appointment->getQuest() === $this) { + $appointment->setQuest(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Review> + */ + #[Groups(['all', 'detail'])] + public function getReviews(): Collection + { + return $this->reviews; + } + + public function addReview(Review $review): static + { + if (!$this->reviews->contains($review)) { + $this->reviews->add($review); + $review->setQuest($this); + } + + return $this; + } + + public function removeReview(Review $review): static + { + if ($this->reviews->removeElement($review)) { + // set the owning side to null (unless already changed) + if ($review->getQuest() === $this) { + $review->setQuest(null); + } + } + + return $this; + } + + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getRating(): float + { + $count = count($this->reviews); + $rating = 0; + foreach ($this->reviews as $review) { + $rating += $review->getRating() ?: 0; + } + + if ($count > 0) { + return $rating / $count; + } + + return 0; + } + + /** + * @return Collection<int, Tag> + */ + #[Groups(['all', 'card', 'detail'])] + public function getTags(): Collection + { + return $this->tags; + } + + public function addTag(Tag $tag): static + { + if (!$this->tags->contains($tag)) { + $this->tags->add($tag); + $tag->addQuest($this); + } + + return $this; + } + + public function removeTag(Tag $tag): static + { + if ($this->tags->removeElement($tag)) { + $tag->removeQuest($this); + } + + return $this; + } + + #[Groups(['all', 'card', 'detail'])] + public function getTheme(): ?Theme + { + return $this->theme; + } + + public function setTheme(?Theme $theme): static + { + $this->theme = $theme; + + return $this; + } + + #[Groups(['all', 'card', 'detail'])] + public function getGenre(): ?Genre + { + return $this->genre; + } + + public function setGenre(?Genre $genre): static + { + $this->genre = $genre; + + return $this; + } +} diff --git a/app/src/Entity/QuestImage.php b/app/src/Entity/QuestImage.php new file mode 100644 index 0000000000000000000000000000000000000000..4e7513cb07ca676758240f4f0f356d4ce2d2aea4 --- /dev/null +++ b/app/src/Entity/QuestImage.php @@ -0,0 +1,91 @@ +<?php + +namespace App\Entity; + +use App\Repository\QuestImageRepository; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: QuestImageRepository::class)] +class QuestImage +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'gallery')] + private ?Quest $quest = null; + + #[ORM\Column(length: 255)] + private ?string $path = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[ORM\Column(length: 255)] + private ?string $type = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getQuest(): ?Quest + { + return $this->quest; + } + + public function setQuest(?Quest $quest): static + { + $this->quest = $quest; + + return $this; + } + + public function getPath(): ?string + { + return $this->path; + } + + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getPublicPath(): ?string + { + if ($this->path !== null) { + return str_replace('/app/public', '', $this->path); + } + return null; + } + + public function setPath(string $path): static + { + $this->path = $path; + + return $this; + } + + #[Groups(['all', 'card', 'detail', 'profile'])] + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(string $type): static + { + $this->type = $type; + + return $this; + } +} diff --git a/app/src/Entity/Review.php b/app/src/Entity/Review.php new file mode 100644 index 0000000000000000000000000000000000000000..3d384446a0a75b9443d641e78f62679e5917ef10 --- /dev/null +++ b/app/src/Entity/Review.php @@ -0,0 +1,164 @@ +<?php + +namespace App\Entity; + +use App\Repository\ReviewRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: ReviewRepository::class)] +class Review +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'reviews')] + #[ORM\JoinColumn(nullable: false)] + private ?User $related_user = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $text = null; + + #[ORM\Column] + private ?int $rating = null; + + #[ORM\ManyToOne(inversedBy: 'reviews')] + #[ORM\JoinColumn(nullable: false)] + private ?Quest $quest = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $create_date = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $update_date = null; + + /** + * @var Collection<int, Like> + */ + #[ORM\OneToMany(targetEntity: Like::class, mappedBy: 'review', orphanRemoval: true)] + private Collection $likes; + + public function __construct() + { + $this->likes = new ArrayCollection(); + } + + #[Groups(['all', 'detail', 'card'])] + public function getId(): ?int + { + return $this->id; + } + + #[Groups(['all', 'detail', 'card'])] + public function getRelatedUser(): ?User + { + return $this->related_user; + } + + public function setRelatedUser(?User $related_user): static + { + $this->related_user = $related_user; + + return $this; + } + + #[Groups(['all', 'detail', 'card'])] + public function getText(): ?string + { + return $this->text; + } + + public function setText(?string $text): static + { + $this->text = $text; + + return $this; + } + + #[Groups(['all', 'detail', 'card'])] + public function getRating(): ?int + { + return $this->rating; + } + + public function setRating(int $rating): static + { + $this->rating = $rating; + + return $this; + } + + public function getQuest(): ?Quest + { + return $this->quest; + } + + public function setQuest(?Quest $quest): static + { + $this->quest = $quest; + + return $this; + } + + #[Groups(['all', 'detail', 'card'])] + public function getCreateDate(): ?\DateTimeInterface + { + return $this->create_date; + } + + public function setCreateDate(\DateTimeInterface $create_date): static + { + $this->create_date = $create_date; + + return $this; + } + + #[Groups(['all', 'detail', 'card'])] + public function getUpdateDate(): ?\DateTimeInterface + { + return $this->update_date; + } + + public function setUpdateDate(\DateTimeInterface $update_date): static + { + $this->update_date = $update_date; + + return $this; + } + + /** + * @return Collection<int, Like> + */ + #[Groups(['all', 'detail', 'card'])] + public function getLikes(): Collection + { + return $this->likes; + } + + public function addLike(Like $like): static + { + if (!$this->likes->contains($like)) { + $this->likes->add($like); + $like->setReview($this); + } + + return $this; + } + + public function removeLike(Like $like): static + { + if ($this->likes->removeElement($like)) { + // set the owning side to null (unless already changed) + if ($like->getReview() === $this) { + $like->setReview(null); + } + } + + return $this; + } +} diff --git a/app/src/Entity/Tag.php b/app/src/Entity/Tag.php new file mode 100644 index 0000000000000000000000000000000000000000..0bbbd7e1bac2c544797708501df3872c8105bbe4 --- /dev/null +++ b/app/src/Entity/Tag.php @@ -0,0 +1,90 @@ +<?php + +namespace App\Entity; + +use App\Repository\TagRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: TagRepository::class)] +class Tag +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + /** + * @var Collection<int, Quest> + */ + #[ORM\ManyToMany(targetEntity: Quest::class, inversedBy: 'tags')] + private Collection $quests; + + public function __construct() + { + $this->quests = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + #[Groups(['all', 'card', 'detail', 'filter'])] + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } + + /** + * @return Collection<int, Quest> + */ + public function getQuests(): Collection + { + return $this->quests; + } + + public function addQuest(Quest $quest): static + { + if (!$this->quests->contains($quest)) { + $this->quests->add($quest); + } + + return $this; + } + + public function removeQuest(Quest $quest): static + { + $this->quests->removeElement($quest); + + return $this; + } +} diff --git a/app/src/Entity/Theme.php b/app/src/Entity/Theme.php new file mode 100644 index 0000000000000000000000000000000000000000..9c898aca619ad086c54e0a58d3e39da66927e8f7 --- /dev/null +++ b/app/src/Entity/Theme.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Entity; + +use App\Repository\ThemeRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ORM\Entity(repositoryClass: ThemeRepository::class)] +class Theme +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; + + #[ORM\Column(length: 255)] + private ?string $name = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE)] + private ?\DateTimeInterface $date = null; + + public function getId(): ?int + { + return $this->id; + } + + #[Groups(['all', 'card', 'detail', 'filter'])] + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } +} diff --git a/app/src/Entity/User.php b/app/src/Entity/User.php index 348b043e42771781be5de044820ca04043e80c65..89d0897184ac05377da592d08c9cf17a5d24caa5 100644 --- a/app/src/Entity/User.php +++ b/app/src/Entity/User.php @@ -76,9 +76,37 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\OneToMany(targetEntity: UserHistory::class, mappedBy: 'related_user', cascade: ['persist', 'remove'],fetch: 'EAGER')] private Collection $userHistories; + /** + * @var Collection<int, Appointment> + */ + #[ORM\OneToMany(targetEntity: Appointment::class, mappedBy: 'related_user')] + private Collection $appointments; + + /** + * @var Collection<int, Review> + */ + #[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'related_user')] + private Collection $reviews; + + /** + * @var Collection<int, Like> + */ + #[ORM\OneToMany(targetEntity: Like::class, mappedBy: 'related_user')] + private Collection $likes; + + /** + * @var Collection<int, Favorite> + */ + #[ORM\OneToMany(targetEntity: Favorite::class, mappedBy: 'related_user')] + private Collection $favorites; + public function __construct() { $this->userHistories = new ArrayCollection(); + $this->appointments = new ArrayCollection(); + $this->reviews = new ArrayCollection(); + $this->likes = new ArrayCollection(); + $this->favorites = new ArrayCollection(); } #[Groups(['all'])] @@ -393,4 +421,127 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $array = json_decode($data, true, 512, JSON_THROW_ON_ERROR); return self::createByArray($array, $groups); } + + /** + * @return Collection<int, Appointment> + */ + #[Groups(['all', 'profile'])] + public function getAppointments(): Collection + { + return $this->appointments; + } + + public function addAppointment(Appointment $appointment): static + { + if (!$this->appointments->contains($appointment)) { + $this->appointments->add($appointment); + $appointment->setRelatedUser($this); + } + + return $this; + } + + public function removeAppointment(Appointment $appointment): static + { + if ($this->appointments->removeElement($appointment)) { + // set the owning side to null (unless already changed) + if ($appointment->getRelatedUser() === $this) { + $appointment->setRelatedUser(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Review> + */ + #[Groups(['all', 'reviews'])] + public function getReviews(): Collection + { + return $this->reviews; + } + + public function addReview(Review $review): static + { + if (!$this->reviews->contains($review)) { + $this->reviews->add($review); + $review->setRelatedUser($this); + } + + return $this; + } + + public function removeReview(Review $review): static + { + if ($this->reviews->removeElement($review)) { + // set the owning side to null (unless already changed) + if ($review->getRelatedUser() === $this) { + $review->setRelatedUser(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Like> + */ + public function getLikes(): Collection + { + return $this->likes; + } + + public function addLike(Like $like): static + { + if (!$this->likes->contains($like)) { + $this->likes->add($like); + $like->setRelatedUser($this); + } + + return $this; + } + + public function removeLike(Like $like): static + { + if ($this->likes->removeElement($like)) { + // set the owning side to null (unless already changed) + if ($like->getRelatedUser() === $this) { + $like->setRelatedUser(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Favorite> + */ + #[Groups(['all', 'favorites'])] + public function getFavorites(): Collection + { + return $this->favorites; + } + + public function addFavorite(Favorite $favorite): static + { + if (!$this->favorites->contains($favorite)) { + $this->favorites->add($favorite); + $favorite->setRelatedUser($this); + } + + return $this; + } + + public function removeFavorite(Favorite $favorite): static + { + if ($this->favorites->removeElement($favorite)) { + // set the owning side to null (unless already changed) + if ($favorite->getRelatedUser() === $this) { + $favorite->setRelatedUser(null); + } + } + + return $this; + } } diff --git a/app/src/Listeners/DateListener.php b/app/src/Listeners/DateListener.php new file mode 100644 index 0000000000000000000000000000000000000000..1cb534ae73b49e9210b92d6044176f0936b4dfdd --- /dev/null +++ b/app/src/Listeners/DateListener.php @@ -0,0 +1,38 @@ +<?php + +namespace App\Listeners; + +use App\Entity\Appointment; +use App\Entity\Like; +use App\Entity\Review; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener; +use Doctrine\ORM\Event\PreFlushEventArgs; +use Doctrine\ORM\Events; + +#[AsEntityListener(event: Events::preFlush, method: 'prePersistAppointment', entity: Appointment::class)] +#[AsEntityListener(event: Events::preFlush, method: 'prePersistReview', entity: Review::class)] +#[AsEntityListener(event: Events::preFlush, method: 'prePersistLike', entity: Like::class)] +class DateListener +{ + public function prePersistAppointment(Appointment $appointment, PreFlushEventArgs $args): void + { + if (!$appointment->getDate()) { + $appointment->setDate(new \DateTime()); + } + } + + public function prePersistReview(Review $review, PreFlushEventArgs $args): void + { + if (!$review->getCreateDate()) { + $review->setCreateDate(new \DateTime()); + } + $review->setUpdateDate(new \DateTime()); + } + + public function prePersistLike(Like $like, PreFlushEventArgs $args): void + { + if (!$like->getDate()) { + $like->setDate(new \DateTime()); + } + } +} \ No newline at end of file diff --git a/app/src/Messenger/Handler/QuestEndMessageHandler.php b/app/src/Messenger/Handler/QuestEndMessageHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..367fae3779bf1fc6989c619c979b13fe789ff50a --- /dev/null +++ b/app/src/Messenger/Handler/QuestEndMessageHandler.php @@ -0,0 +1,62 @@ +<?php + +namespace App\Messenger\Handler; + +use App\Entity\Quest; +use App\Messenger\Message\QuestMessage; +use App\Messenger\Objects\QuestsEnd; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\MessageBusInterface; + +#[AsMessageHandler] +class QuestEndMessageHandler +{ + public function __construct( + protected MessageBusInterface $bus, + private ManagerRegistry $doctrine, + private string $fromEmail, + ) + { + } + + /** + * Обработка пиÑьма из очереди + * + * @param QuestsEnd $message + * + * @return void + */ + public function __invoke(QuestsEnd $message): void + { + $quests = $this->doctrine->getRepository(Quest::class)->getEndQuests(); + + foreach ($quests as $quest) { + $appointments = $quest->getAppointments(); + $questName = $quest->getName(); + foreach ($appointments as $appointment) { + $user = $appointment->getRelatedUser(); + if ($user) { + $userName = $user->getFullName(); + $questMessage = new QuestMessage( + $this->fromEmail, + $user->getEmail(), + "КвеÑÑ‚ \"{$questName}\" прошёл!", + <<<HTML + <div> + ЗдравÑтвуйте, {$userName}! + </div> + <div> + КвеÑÑ‚ {$questName} уже прошёл, какие у Ð’Ð°Ñ Ð¾ÑталиÑÑŒ впечатлениÑ? + </div> + <div> + ОÑтавьте отзыв на Ñайте + </div> + HTML + ); + $this->bus->dispatch($questMessage); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/Messenger/Handler/QuestMessageHandler.php b/app/src/Messenger/Handler/QuestMessageHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..164ea6636cef517430d9b2f11a61b5bfc24d511b --- /dev/null +++ b/app/src/Messenger/Handler/QuestMessageHandler.php @@ -0,0 +1,44 @@ +<?php + +namespace App\Messenger\Handler; + +use App\Messenger\Message\QuestMessage; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Mime\Email; + +#[AsMessageHandler] +class QuestMessageHandler +{ + public function __construct( + protected MessageBusInterface $bus, + private MailerInterface $mailer + ) + { + } + + /** + * Обработка пиÑьма из очереди + * + * @param QuestMessage $message + * + * @return void + * @throws TransportExceptionInterface + */ + public function __invoke(QuestMessage $message): void + { + $mail = new Email(); + $mail + ->subject($message->getSubject()) + ->from($message->getFrom()) + ->to($message->getTo()) + ->html($message->getBody()); + try { + $this->mailer->send($mail); + } catch (\Exception $exception) { + throw new \Exception('Ошибка отправки пиÑьма'); + } + } +} \ No newline at end of file diff --git a/app/src/Messenger/Handler/QuestStartMessageHandler.php b/app/src/Messenger/Handler/QuestStartMessageHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..4aac3f7532a8736d98ed612682148b67fe3bc0ed --- /dev/null +++ b/app/src/Messenger/Handler/QuestStartMessageHandler.php @@ -0,0 +1,62 @@ +<?php + +namespace App\Messenger\Handler; + +use App\Entity\Quest; +use App\Messenger\Message\QuestMessage; +use App\Messenger\Objects\QuestsStart; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\MessageBusInterface; + +#[AsMessageHandler] +class QuestStartMessageHandler +{ + public function __construct( + protected MessageBusInterface $bus, + private ManagerRegistry $doctrine, + private string $fromEmail, + ) + { + } + + /** + * Обработка пиÑьма из очереди + * + * @param QuestsStart $message + * + * @return void + */ + public function __invoke(QuestsStart $message): void + { + $quests = $this->doctrine->getRepository(Quest::class)->getStartQuests(); + + foreach ($quests as $quest) { + $appointments = $quest->getAppointments(); + $questName = $quest->getName(); + foreach ($appointments as $appointment) { + $user = $appointment->getRelatedUser(); + if ($user) { + $userName = $user->getFullName(); + $questMessage = new QuestMessage( + $this->fromEmail, + $user->getEmail(), + "КвеÑÑ‚ \"{$questName}\" Ñтартует уже через 3 днÑ!", + <<<HTML + <div> + ЗдравÑтвуйте, {$userName}! + </div> + <div> + КвеÑÑ‚ "{$questName}" Ñтартует уже через 3 днÑ! + </div> + <div> + Чтобы не забыть, запланируйте его у ÑÐµÐ±Ñ Ð² календаре! + </div> + HTML + ); + $this->bus->dispatch($questMessage); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/Messenger/Message/QuestMessage.php b/app/src/Messenger/Message/QuestMessage.php new file mode 100644 index 0000000000000000000000000000000000000000..042b4e9c35e60594c1138a594de3210a6b02d448 --- /dev/null +++ b/app/src/Messenger/Message/QuestMessage.php @@ -0,0 +1,63 @@ +<?php + +namespace App\Messenger\Message; + +class QuestMessage +{ + public function __construct( + public ?string $from, + public ?string $to, + public ?string $subject, + public ?string $body + ) + { + } + + public function getSubject(): string + { + return $this->subject; + } + + public function setSubject(string $subject): self + { + $this->subject = $subject; + + return $this; + } + + public function getBody(): string + { + return $this->body; + } + + public function setBody(string $body): self + { + $this->body = $body; + + return $this; + } + + public function getFrom(): string + { + return $this->from; + } + + public function setFrom(string $from): self + { + $this->from = $from; + + return $this; + } + + public function getTo(): string + { + return $this->to; + } + + public function setTo(string $to): self + { + $this->to = $to; + + return $this; + } +} \ No newline at end of file diff --git a/app/src/Messenger/Objects/QuestsEnd.php b/app/src/Messenger/Objects/QuestsEnd.php new file mode 100644 index 0000000000000000000000000000000000000000..2f6930a660d40f1f83079b1db8d543f7f3ab641e --- /dev/null +++ b/app/src/Messenger/Objects/QuestsEnd.php @@ -0,0 +1,8 @@ +<?php + +namespace App\Messenger\Objects; + +class QuestsEnd +{ + +} \ No newline at end of file diff --git a/app/src/Messenger/Objects/QuestsStart.php b/app/src/Messenger/Objects/QuestsStart.php new file mode 100644 index 0000000000000000000000000000000000000000..2ffe96cf7a48ecb2dba7eda022737ff796d46d18 --- /dev/null +++ b/app/src/Messenger/Objects/QuestsStart.php @@ -0,0 +1,8 @@ +<?php + +namespace App\Messenger\Objects; + +class QuestsStart +{ + +} \ No newline at end of file diff --git a/app/src/Redis/Redis.php b/app/src/Redis/Redis.php new file mode 100644 index 0000000000000000000000000000000000000000..2afbce290d2bd1fc1a459b93286d990484cdedf6 --- /dev/null +++ b/app/src/Redis/Redis.php @@ -0,0 +1,78 @@ +<?php + +namespace App\Redis; + +use Exception; +use Psr\Cache\InvalidArgumentException; +use Symfony\Component\Cache\Adapter\RedisAdapter; + +class Redis +{ + private static self $instance; + + private RedisAdapter $cache; + + /** + * @throws Exception + */ + private function __construct() + { + $client = RedisAdapter::createConnection('redis://redis'); + $client->connect('127.0.0.1', $_ENV['REDIS_PORT']); + if ($client->isConnected()) { + $this->cache = new RedisAdapter($client); + } else { + throw new \RuntimeException('Redis не подключен'); + } + } + + public static function getInstance(): Redis + { + if (!isset(self::$instance)) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * ЗапиÑÑŒ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ + * + * @param string $key + * @param mixed $value + * + * @return $this + * @throws InvalidArgumentException + */ + public function set(string $key, mixed $value): self + { + $item = $this->cache->getItem($key); + $item->set($value); + $this->cache->save($item); + + return $this; + } + + /** + * @param string $key + * + * @return mixed + * @throws InvalidArgumentException + */ + public function get(string $key): mixed + { + return $this->cache->getItem($key)->get(); + } + + public function delete(string $key): self + { + $this->cache->deleteItem($key); + + return $this; + } + + public function has(string $key): bool + { + return $this->cache->hasItem($key); + } +} \ No newline at end of file diff --git a/app/src/Redis/RedisFilter.php b/app/src/Redis/RedisFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..95a3b88883311c556e51e519210d1e4309ac4b6c --- /dev/null +++ b/app/src/Redis/RedisFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace App\Redis; + +use App\Service\Dto\DtoServiceInterface; + +class RedisFilter +{ + private int $userId; + private Redis $redis; + + public function __construct(int $userId) + { + $this->userId = $userId; + $this->redis = Redis::getInstance(); + } + + public function get(): ?DtoServiceInterface + { + $filter = $this->redis->get($this->getCode()); + + if ($filter instanceof DtoServiceInterface) { + return $filter; + } + + return null; + } + + public function set(DtoServiceInterface $filterDto): void + { + $this->redis->set($this->getCode(), $filterDto); + } + + private function getCode(): string + { + return 'filter_' . $this->userId; + } +} \ No newline at end of file diff --git a/app/src/Repository/AppointmentRepository.php b/app/src/Repository/AppointmentRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..5bf5ad8f4d0293f70b9c1354995c188976cad8bc --- /dev/null +++ b/app/src/Repository/AppointmentRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\Appointment; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Appointment> + */ +class AppointmentRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Appointment::class); + } + + // /** + // * @return Appointment[] Returns an array of Appointment objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('a') + // ->andWhere('a.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('a.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Appointment + // { + // return $this->createQueryBuilder('a') + // ->andWhere('a.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/FavoriteRepository.php b/app/src/Repository/FavoriteRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..b96991e965d526b707f7b2ee64e0d832f367de58 --- /dev/null +++ b/app/src/Repository/FavoriteRepository.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Repository; + +use App\Entity\Favorite; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Favorite> + */ +class FavoriteRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Favorite::class); + } + + public function findAllByUser(int $userId): array + { + return $this->createQueryBuilder('f') + ->setParameter('user_id', $userId) + ->leftJoin('f.related_user', 'user') + ->andWhere('user.id = :user_id') + ->getQuery() + ->getResult(); + } + + // /** + // * @return Favorite[] Returns an array of Favorite objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('f') + // ->andWhere('f.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('f.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Favorite + // { + // return $this->createQueryBuilder('f') + // ->andWhere('f.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/GenreRepository.php b/app/src/Repository/GenreRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..e29a15de34af6d65bf93a4387e9a997c3b5d885a --- /dev/null +++ b/app/src/Repository/GenreRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\Genre; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Genre> + */ +class GenreRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Genre::class); + } + + // /** + // * @return Genre[] Returns an array of Genre objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('g') + // ->andWhere('g.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('g.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Genre + // { + // return $this->createQueryBuilder('g') + // ->andWhere('g.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/LikeRepository.php b/app/src/Repository/LikeRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..123d631fc85476df0df5d8d0d5730382a9c8d1e5 --- /dev/null +++ b/app/src/Repository/LikeRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\Like; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Like> + */ +class LikeRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Like::class); + } + + // /** + // * @return Like[] Returns an array of Like objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('l') + // ->andWhere('l.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('l.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Like + // { + // return $this->createQueryBuilder('l') + // ->andWhere('l.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/QuestImageRepository.php b/app/src/Repository/QuestImageRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..679bfe74356d1559b7da47b8f698f1843e2fd848 --- /dev/null +++ b/app/src/Repository/QuestImageRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\QuestImage; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<QuestImage> + */ +class QuestImageRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, QuestImage::class); + } + +// /** +// * @return QuestImage[] Returns an array of QuestImage objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('q') +// ->andWhere('q.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('q.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?QuestImage +// { +// return $this->createQueryBuilder('q') +// ->andWhere('q.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/app/src/Repository/QuestRepository.php b/app/src/Repository/QuestRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..e44dfde606e8afb8a08a632cc9025e1faffe3d55 --- /dev/null +++ b/app/src/Repository/QuestRepository.php @@ -0,0 +1,171 @@ +<?php + +namespace App\Repository; + +use App\Entity\Quest; +use App\Redis\RedisFilter; +use App\Service\Dto\Classes\FilterDto; +use DateInterval; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Quest> + */ +class QuestRepository extends ServiceEntityRepository +{ + public const SORT_TYPES = [ + 'ASC', + 'DESC' + ]; + public const SORT_FIELDS = [ + 'name', + 'date', + 'final_date' + ]; + + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Quest::class); + } + + public function findAllByFilter(int $userId): array + { + $queryBuilder = $this->getFilterQuery($userId); + return $queryBuilder->getQuery()->getResult(); + } + + public function findCompletedByFilter(int $userId): array + { + $queryBuilder = $this->getFilterQuery($userId); + + $currentDate = new \DateTime(); + $queryBuilder->setParameter('current', $currentDate) + ->andWhere('q.date < :current'); + + $queryBuilder->setParameter('user_id', $userId) + ->leftJoin('q.appointments', 'appointments') + ->andWhere('appointments.related_user = :user_id'); + + return $queryBuilder->getQuery()->getResult(); + } + + public function findOneById(int $id, int $userId): ?Quest + { + $queryBuilder = $this->getBaseQuery($userId); + $queryBuilder->setParameter('detail_id', $id) + ->andWhere('q.id = :detail_id'); + return $queryBuilder->getQuery() + ->enableResultCache(3600, $id) + ->getOneOrNullResult(); + } + + private function getBaseQuery(int $userId): QueryBuilder + { + return $this->createQueryBuilder('q'); + } + + private function getFilterQuery(int $userId): QueryBuilder + { + $queryBuilder = $this->getBaseQuery($userId); + + $redisFilter = new RedisFilter($userId); + $filter = $redisFilter->get(); + if ($filter instanceof FilterDto) { + if (!empty($filter->search)) { + $text = $filter->search; + $queryBuilder->setParameter('search', "%$text%") + ->orWhere('q.name LIKE :search') + ->orWhere('q.short_description LIKE :search') + ->orWhere('q.full_description LIKE :search'); + } + if ($genres = $filter->genres) { + $queryBuilder->setParameter('genres', $genres) + ->leftJoin('q.genre', 'genre') + ->andWhere('genre.name IN (:genres)'); + } + if ($themes = $filter->themes) { + $queryBuilder->setParameter('themes', $themes) + ->leftJoin('q.theme', 'theme') + ->andWhere('theme.name IN (:themes)'); + } + if ($tags = $filter->tags) { + $queryBuilder->setParameter('tags', $tags) + ->leftJoin('q.tags', 'tags') + ->andWhere('tags.name IN (:tags)'); + } + if ($filter->sort + && $filter->sortField + && in_array($filter->sort, self::SORT_TYPES, true) + && in_array($filter->sortField, self::SORT_FIELDS, true) + ) { + $queryBuilder->orderBy('q.'.$filter->sortField, $filter->sort); + } + } + return $queryBuilder; + } + + public function getStartQuests(): array + { + $queryBuilder = $this->createQueryBuilder('q'); + + $startDate = new \DateTime(); + $startDate->setTime(0,0); + $startDate->add(new DateInterval('P3D')); + $endDate = new \DateTime(); + $endDate->setTime(23,59, 59); + $endDate->add(new DateInterval('P3D')); + + $queryBuilder->setParameter('start', $startDate) + ->setParameter('end', $endDate) + ->andWhere('q.date >= :start') + ->andWhere('q.date <= :end'); + + return $queryBuilder->getQuery()->getResult(); + } + + public function getEndQuests(): array + { + $queryBuilder = $this->createQueryBuilder('q'); + + $startDate = new \DateTime(); + $startDate->setTime(0,0); + $startDate->sub(new DateInterval('P1D')); + $endDate = new \DateTime(); + $endDate->setTime(23,59, 59); + $endDate->sub(new DateInterval('P1D')); + + $queryBuilder->setParameter('start', $startDate) + ->setParameter('end', $endDate) + ->andWhere('q.date >= :start') + ->andWhere('q.date <= :end'); + + return $queryBuilder->getQuery()->getResult(); + } + + // /** + // * @return Quest[] Returns an array of Quest objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('q') + // ->andWhere('q.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('q.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Quest + // { + // return $this->createQueryBuilder('q') + // ->andWhere('q.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/ReviewRepository.php b/app/src/Repository/ReviewRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..32b5d5d1bf40d4354163d2a94c9913c53e964ed8 --- /dev/null +++ b/app/src/Repository/ReviewRepository.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Repository; + +use App\Entity\Review; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Review> + */ +class ReviewRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Review::class); + } + + public function findAllByUser(int $userId): array + { + return $this->createQueryBuilder('r') + ->setParameter('user_id', $userId) + ->leftJoin('r.related_user', 'user') + ->andWhere('user.id = :user_id') + ->getQuery() + ->getResult(); + } + + // /** + // * @return Review[] Returns an array of Review objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('r') + // ->andWhere('r.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('r.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Review + // { + // return $this->createQueryBuilder('r') + // ->andWhere('r.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/TagRepository.php b/app/src/Repository/TagRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..1b3c116d0fdc4a553a8adf4a2e557c41197fb897 --- /dev/null +++ b/app/src/Repository/TagRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\Tag; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Tag> + */ +class TagRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Tag::class); + } + + // /** + // * @return Tag[] Returns an array of Tag objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('t') + // ->andWhere('t.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('t.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Tag + // { + // return $this->createQueryBuilder('t') + // ->andWhere('t.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Repository/ThemeRepository.php b/app/src/Repository/ThemeRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..902276194ab43c6501f431014977c0938cec7086 --- /dev/null +++ b/app/src/Repository/ThemeRepository.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Repository; + +use App\Entity\Theme; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Theme> + */ +class ThemeRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Theme::class); + } + + // /** + // * @return Theme[] Returns an array of Theme objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('t') + // ->andWhere('t.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('t.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Theme + // { + // return $this->createQueryBuilder('t') + // ->andWhere('t.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/app/src/Scheduler/QuestProvider.php b/app/src/Scheduler/QuestProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..84f6a5ebc3b7031e49c27fb31746a3bcd22b874f --- /dev/null +++ b/app/src/Scheduler/QuestProvider.php @@ -0,0 +1,26 @@ +<?php + +namespace App\Scheduler; + +use App\Messenger\Objects\QuestsEnd; +use App\Messenger\Objects\QuestsStart; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\RecurringMessage; +use Symfony\Component\Scheduler\Schedule; +use Symfony\Component\Scheduler\ScheduleProviderInterface; + +#[AsSchedule(name: 'quests')] +class QuestProvider implements ScheduleProviderInterface +{ + public function getSchedule(): Schedule + { + $schedule = new Schedule(); + + $schedule->add( + RecurringMessage::every('1 day', new QuestsStart(), from: '09:00'), + RecurringMessage::every('1 day', new QuestsEnd(), from: '10:00') + ); + + return $schedule; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/CreateReview.php b/app/src/Service/Action/Classes/CreateReview.php new file mode 100644 index 0000000000000000000000000000000000000000..056714718ad1ae753fec60432ea23b3db92aa643 --- /dev/null +++ b/app/src/Service/Action/Classes/CreateReview.php @@ -0,0 +1,77 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\CreateReviewDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.review.create', public: true)] +class CreateReview extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.review.create')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var CreateReviewDto $dto */ + $dto = $this->getDto(); + if ($dto->questId && $this->user->getId()) { + /** @var Quest|null $quest */ + $quest = $this->doctrine->getRepository(Quest::class)->findOneById($dto->questId, $this->user->getId()); + if ($quest) { + $reviews = $quest->getReviews(); + $exists = null; + foreach ($reviews as $review) { + if ($review->getRelatedUser() === $this->user) { + $exists = $review; + } + } + if ($exists) { + $this->responseService->addError('Ваш отзыв на квеÑÑ‚ уже ÑущеÑтвует'); + } else { + try { + $newReview = new Review(); + $newReview->setRelatedUser($this->user); + $newReview->setQuest($quest); + $newReview->setText($dto->text); + $newReview->setRating($dto->rating); + $em = $this->doctrine->getManager(); + $em->persist($newReview); + $em->flush(); + $this->responseService->addMessage('Отзыв добавлен'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð·Ñ‹Ð²Ð°'); + } + } + } + } else { + $this->responseService->addError('Ðе передан ID квеÑта'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/DeleteReview.php b/app/src/Service/Action/Classes/DeleteReview.php new file mode 100644 index 0000000000000000000000000000000000000000..63cd87e576c1fd846c62a7162fe42644c0e38d22 --- /dev/null +++ b/app/src/Service/Action/Classes/DeleteReview.php @@ -0,0 +1,61 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.review.delete', public: true)] +class DeleteReview extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + if ($dto->id) { + $review = $this->doctrine->getRepository(Review::class)->find($dto->id); + if ($review) { + try { + $em = $this->doctrine->getManager(); + $em->remove($review); + $em->flush(); + $this->responseService->addMessage('Отзыв удален'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð·Ñ‹Ð²Ð°'); + } + } else { + $this->responseService->addError('Ðе найден отзыв'); + } + } else { + $this->responseService->addError('Ðе получен ID отзыва'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetFavorites.php b/app/src/Service/Action/Classes/GetFavorites.php new file mode 100644 index 0000000000000000000000000000000000000000..bdf95869af888073c6280e2fe880a2cd06281a87 --- /dev/null +++ b/app/src/Service/Action/Classes/GetFavorites.php @@ -0,0 +1,46 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Favorite; +use App\Service\Action\UserBaseActionService; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.favorites', public: true)] +class GetFavorites extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.favorites')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + if ($userId = $this->user->getId()) { + $this->responseService->setData($this->doctrine->getRepository(Favorite::class)->findAllByUser($userId)); + } else { + $this->responseService->addError('Пользователь не Ñохранен'); + } + } + + public function needDto(): bool + { + return false; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetFilter.php b/app/src/Service/Action/Classes/GetFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..194da5c856b9bf17c4e761591b30edf3fc5dcf07 --- /dev/null +++ b/app/src/Service/Action/Classes/GetFilter.php @@ -0,0 +1,54 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Redis\Redis; +use App\Redis\RedisFilter; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\FilterDto; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.filter.get', public: true)] +class GetFilter extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.filter')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + if ($this->user->getId()) { + $redisFilter = new RedisFilter($this->user->getId()); + $filter = $redisFilter->get(); + if ($filter instanceof FilterDto) { + $this->responseService->setData($filter); + } else { + $this->responseService->addError('Ðеверный формат фильтра'); + } + } else { + $this->responseService->addError('Пользователь не Ñохранен'); + } + } + + public function needDto(): bool + { + return false; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetFilterParams.php b/app/src/Service/Action/Classes/GetFilterParams.php new file mode 100644 index 0000000000000000000000000000000000000000..7b3bbed44df53927e8674e60d492228eda59307d --- /dev/null +++ b/app/src/Service/Action/Classes/GetFilterParams.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Genre; +use App\Entity\Tag; +use App\Entity\Theme; +use App\Service\Action\BaseActionService; +use App\Service\Response\Classes\Data\Filter; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.filter.params', public: true)] +class GetFilterParams extends BaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.filter.params')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + $filter = new Filter(); + + $filter->themes = $this->doctrine->getRepository(Theme::class)->findAll(); + + $filter->genres = $this->doctrine->getRepository(Genre::class)->findAll(); + + $filter->tags = $this->doctrine->getRepository(Tag::class)->findAll(); + + $this->responseService->setData($filter); + } + + public function needDto(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetProfileQuests.php b/app/src/Service/Action/Classes/GetProfileQuests.php new file mode 100644 index 0000000000000000000000000000000000000000..2f00de42f4f7418c435f986fed1f3750f23e8bdf --- /dev/null +++ b/app/src/Service/Action/Classes/GetProfileQuests.php @@ -0,0 +1,46 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Service\Action\UserBaseActionService; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.profile.quests', public: true)] +class GetProfileQuests extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.quests')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + if ($userId = $this->user->getId()) { + $this->responseService->setData($this->doctrine->getRepository(Quest::class)->findCompletedByFilter($userId)); + } else { + $this->responseService->addError('Пользователь не Ñохранен'); + } + } + + public function needDto(): bool + { + return false; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetQuest.php b/app/src/Service/Action/Classes/GetQuest.php new file mode 100644 index 0000000000000000000000000000000000000000..d593c25cb706eb5d857a506f132051b1790bdea6 --- /dev/null +++ b/app/src/Service/Action/Classes/GetQuest.php @@ -0,0 +1,64 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest', public: true)] +class GetQuest extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.quest')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + + if ($dto->id && $this->user->getId()) { + $quest = $this->doctrine->getRepository(Quest::class)->findOneById($dto->id, $this->user->getId()); + if ($quest) { + $this->responseService->setData($quest); + } else { + $this->responseService->addError('КвеÑÑ‚ не найден'); + } + } else { + $this->responseService->addError('Ðе получен Id'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetQuests.php b/app/src/Service/Action/Classes/GetQuests.php new file mode 100644 index 0000000000000000000000000000000000000000..5abf867f6df8cbf7034ee776d8b0fad790cb1374 --- /dev/null +++ b/app/src/Service/Action/Classes/GetQuests.php @@ -0,0 +1,47 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Service\Action\UserBaseActionService; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quests', public: true)] +class GetQuests extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.quests')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + + public function runAction(): void + { + if ($userId = $this->user->getId()) { + $this->responseService->setData($this->doctrine->getRepository(Quest::class)->findAllByFilter($userId)); + } else { + $this->responseService->addError('Пользователь не Ñохранен'); + } + } + + public function needDto(): bool + { + return false; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/GetReviews.php b/app/src/Service/Action/Classes/GetReviews.php new file mode 100644 index 0000000000000000000000000000000000000000..f0f24c5d61234edf2bab08a98a8d01e5bea522c8 --- /dev/null +++ b/app/src/Service/Action/Classes/GetReviews.php @@ -0,0 +1,47 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.reviews', public: true)] +class GetReviews extends UserBaseActionService +{ + #[Required] public function initResponse( + #[Autowire(service: 'response.reviews')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + if ($userId = $this->user->getId()) { + $this->responseService->setData($this->doctrine->getRepository(Review::class)->findAllByUser($userId)); + } else { + $this->responseService->addError('Пользователь не Ñохранен'); + } + } + + public function needDto(): bool + { + return false; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/LikeReview.php b/app/src/Service/Action/Classes/LikeReview.php new file mode 100644 index 0000000000000000000000000000000000000000..2560c9aa469475bd713223ddab64dec980d9cfdc --- /dev/null +++ b/app/src/Service/Action/Classes/LikeReview.php @@ -0,0 +1,78 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Like; +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.review.like', public: true)] +class LikeReview extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + if ($dto->id) { + $review = $this->doctrine->getRepository(Review::class)->find($dto->id); + if ($review) { + $likes = $review->getLikes(); + $exists = null; + foreach ($likes as $like) { + if ($like->getRelatedUser() === $this->user) { + $exists = $like; + } + } + if ($exists) { + $this->responseService->addError('Ð’Ñ‹ уже оценили отзыв'); + } elseif ($review->getRelatedUser() === $this->user) { + $this->responseService->addError('ÐÐµÐ»ÑŒÐ·Ñ Ð¾Ñ†ÐµÐ½Ð¸Ñ‚ÑŒ Ñвой отзыв'); + } else { + try { + $newLike = new Like(); + $newLike->setRelatedUser($this->user); + $newLike->setReview($review); + $em = $this->doctrine->getManager(); + $em->persist($newLike); + $em->flush(); + $this->responseService->addMessage('Вам нравитÑÑ Ð¾Ñ‚Ð·Ñ‹Ð²'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка оценки отзыва'); + } + } + } else { + $this->responseService->addError('Ðе найден отзыв'); + } + } else { + $this->responseService->addError('Ðе получен ID отзыва'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/SetFilter.php b/app/src/Service/Action/Classes/SetFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..45803cc63471a366ac9cbca99cdd8a06279b2023 --- /dev/null +++ b/app/src/Service/Action/Classes/SetFilter.php @@ -0,0 +1,60 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Redis\Redis; +use App\Redis\RedisFilter; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\DtoServiceInterface; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.filter.set', public: true)] +class SetFilter extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.filter')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + #[Required] public function initResponse( + #[Autowire(service: 'response.quests')] + ResponseServiceInterface $responseService + ): void + { + parent::initResponse($responseService); + } + + public function runAction(): void + { + $dto = $this->getDto(); + if ($this->user->getId() && $dto) { + $redisFilter = new RedisFilter($this->user->getId()); + $redisFilter->set($dto); + $this->responseService->setData($this->doctrine->getRepository(Quest::class)->findAllByFilter($this->user->getId()) ?: []); + } else { + $this->responseService->addError('Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð°'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/SubscribeQuest.php b/app/src/Service/Action/Classes/SubscribeQuest.php new file mode 100644 index 0000000000000000000000000000000000000000..f7a460bc09fc5fc92af8b104a97418ee2593649a --- /dev/null +++ b/app/src/Service/Action/Classes/SubscribeQuest.php @@ -0,0 +1,83 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Appointment; +use App\Entity\Quest; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.subscribe', public: true)] +class SubscribeQuest extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + if ($dto->id && $this->user->getId()) { + /** @var Quest|null $quest */ + $quest = $this->doctrine->getRepository(Quest::class)->findOneById($dto->id, $this->user->getId()); + if ($quest) { + if ($quest->getAppointmentCount() >= $quest->getMaxAppointments()) { + $this->responseService->addError('МакÑимальное количеÑтво учаÑтников'); + } elseif (new \DateTime() > $quest->getFinalDate()) { + $this->responseService->addError('Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ð¸Ñи на квеÑÑ‚ окончено, запиÑатьÑÑ Ð½ÐµÐ»ÑŒÐ·Ñ'); + } else { + $appointments = $quest->getAppointments(); + $exists = false; + foreach ($appointments as $appointment) { + if ($appointment->getRelatedUser() === $this->user) { + $exists = true; + } + } + if (!$exists) { + try { + $newAppointment = new Appointment(); + $newAppointment->setQuest($quest); + $newAppointment->setRelatedUser($this->user); + $em = $this->doctrine->getManager(); + $em->persist($newAppointment); + $em->flush(); + $this->responseService->addMessage('Ð’Ñ‹ запиÑаны на квеÑÑ‚'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка запиÑи на квеÑÑ‚'); + } + } else { + $this->responseService->addError('Ð’Ñ‹ уже запиÑаны'); + } + } + } else { + $this->responseService->addError('КвеÑÑ‚ не найден'); + } + } else { + $this->responseService->addError('Ðе получен Id'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/UnlikeReview.php b/app/src/Service/Action/Classes/UnlikeReview.php new file mode 100644 index 0000000000000000000000000000000000000000..d03566e597ae7c0b0ed95be31b3387260ee3291b --- /dev/null +++ b/app/src/Service/Action/Classes/UnlikeReview.php @@ -0,0 +1,73 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Like; +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.review.unlike', public: true)] +class UnlikeReview extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + if ($dto->id) { + $review = $this->doctrine->getRepository(Review::class)->find($dto->id); + if ($review) { + $likes = $review->getLikes(); + $exists = null; + foreach ($likes as $like) { + if ($like->getRelatedUser() === $this->user) { + $exists = $like; + } + } + if ($exists) { + try { + $em = $this->doctrine->getManager(); + $em->remove($exists); + $em->flush(); + $this->responseService->addMessage('Вам больше не нравитÑÑ Ð¾Ñ‚Ð·Ñ‹Ð²'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка оценки отзыва'); + } + } else { + $this->responseService->addError('Оценка отзыва не найдена'); + } + } else { + $this->responseService->addError('Ðе найден отзыв'); + } + } else { + $this->responseService->addError('Ðе получен ID отзыва'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/UnsubscribeQuest.php b/app/src/Service/Action/Classes/UnsubscribeQuest.php new file mode 100644 index 0000000000000000000000000000000000000000..34b7ac70a71b18a5c2863633571160a13dd2c9c1 --- /dev/null +++ b/app/src/Service/Action/Classes/UnsubscribeQuest.php @@ -0,0 +1,76 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\IdDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.unsubscribe', public: true)] +class UnsubscribeQuest extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.id')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var IdDto $dto */ + $dto = $this->getDto(); + if ($dto->id && $this->user->getId()) { + $quest = $this->doctrine->getRepository(Quest::class)->findOneById($dto->id, $this->user->getId()); + if ($quest) { + if (new \DateTime() > $quest->getFinalDate()) { + $this->responseService->addError('Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ð¸Ñи на квеÑÑ‚ окончено, отпиÑатьÑÑ Ð½ÐµÐ»ÑŒÐ·Ñ'); + } else { + $appointments = $quest->getAppointments(); + $exists = null; + foreach ($appointments as $appointment) { + if ($appointment->getRelatedUser() === $this->user) { + $exists = $appointment; + } + } + if ($exists) { + try { + $em = $this->doctrine->getManager(); + $em->remove($exists); + $em->flush(); + $this->responseService->addMessage('Ð’Ñ‹ отпиÑаны от квеÑта'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка отпиÑки от квеÑта'); + } + } else { + $this->responseService->addError('Ð’Ñ‹ не подпиÑаны на квеÑÑ‚'); + } + } + } else { + $this->responseService->addError('КвеÑÑ‚ не найден'); + } + } else { + $this->responseService->addError('Ðе получен Id'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Action/Classes/UpdateReview.php b/app/src/Service/Action/Classes/UpdateReview.php new file mode 100644 index 0000000000000000000000000000000000000000..ede7432fa9d5015bdf6823c77bbeb51f187c3569 --- /dev/null +++ b/app/src/Service/Action/Classes/UpdateReview.php @@ -0,0 +1,65 @@ +<?php + +namespace App\Service\Action\Classes; + +use App\Entity\Quest; +use App\Entity\Review; +use App\Service\Action\UserBaseActionService; +use App\Service\Dto\Classes\CreateReviewDto; +use App\Service\Dto\Classes\UpdateReviewDto; +use App\Service\Dto\DtoServiceInterface; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Contracts\Service\Attribute\Required; + +#[AsAlias(id: 'action.quest.review.update', public: true)] +class UpdateReview extends UserBaseActionService +{ + #[Required] public function initDto( + #[Autowire(service: 'dto.review.update')] + DtoServiceInterface $dtoService + ): void + { + parent::initDto($dtoService); + } + + public function runAction(): void + { + /** @var UpdateReviewDto $dto */ + $dto = $this->getDto(); + if ($dto->reviewId && $this->user->getId()) { + $review = $this->doctrine->getRepository(Review::class)->find($dto->reviewId); + if ($review) { + try { + $review->setText($dto->text); + $review->setRating($dto->rating); + $em = $this->doctrine->getManager(); + $em->persist($review); + $em->flush(); + $this->responseService->addMessage('Отзыв обновлен'); + } catch (\Exception $exception) { + $this->responseService->addError('Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð·Ñ‹Ð²Ð°'); + } + } else { + $this->responseService->addError('Ðе найден отзыв'); + } + } else { + $this->responseService->addError('Ðе передан ID отзыва'); + } + } + + public function needDto(): bool + { + return true; + } + + public function checkDelete(): bool + { + return true; + } + + public function checkConfirm(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/src/Service/Dto/BaseDto.php b/app/src/Service/Dto/BaseDto.php index 363af9757865249aafbea400550fbf5999a32af0..220b8155cc64f939629a94cb715a37b30dccb953 100644 --- a/app/src/Service/Dto/BaseDto.php +++ b/app/src/Service/Dto/BaseDto.php @@ -14,15 +14,25 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Service\Attribute\Required; abstract class BaseDto implements DtoServiceInterface { private ?Request $request = null; + private ?ValidatorInterface $validator = null; - public function __construct( - private ?ValidatorInterface $validator, - ?RequestStack $requestStack = null, - ) + #[Required] + public function initValidator( + ?ValidatorInterface $validator + ): void + { + $this->validator = $validator; + } + + #[Required] + public function initRequest( + ?RequestStack $requestStack = null + ): void { if ($requestStack) { $this->request = $requestStack->getCurrentRequest(); @@ -39,6 +49,23 @@ abstract class BaseDto implements DtoServiceInterface public function getClass(): ?DtoServiceInterface { if ($this->request) { + return self::getClassByData($this->request->getContent()); + } + + return null; + } + + public static function getClassByArray(?array $data = null): ?DtoServiceInterface + { + if (empty($data)) { + return null; + } + return self::getClassByData(json_encode($data, JSON_THROW_ON_ERROR) ?: ''); + } + + private static function getClassByData(string $data): ?DtoServiceInterface + { + if (!empty($data)) { try { $normalizer = new ObjectNormalizer( null, @@ -50,12 +77,11 @@ abstract class BaseDto implements DtoServiceInterface [$normalizer, new DateTimeNormalizer()], [new JsonEncoder()] ); - return $serializer->deserialize($this->request->getContent(), static::class, 'json'); + return $serializer->deserialize($data, static::class, 'json'); } catch (\Exception $exception) { return null; } } - return null; } diff --git a/app/src/Service/Dto/Classes/CreateReviewDto.php b/app/src/Service/Dto/Classes/CreateReviewDto.php new file mode 100644 index 0000000000000000000000000000000000000000..c12ca3712e4783df79a57c66aefd93b73c205b41 --- /dev/null +++ b/app/src/Service/Dto/Classes/CreateReviewDto.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Service\Dto\Classes; + +use App\Service\Dto\BaseDto; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Validator\Constraints as Assert; + +#[AsAlias(id: 'dto.review.create', public: true)] +class CreateReviewDto extends BaseDto +{ + #[Assert\NotBlank( + message: 'Ðе получен текÑÑ‚ отзыва.', + )] + public ?string $text = null; + + #[Assert\Range( + notInRangeMessage: 'Оценку можно поÑтавить от {{ min }} до {{ max }}.', + min: 0, + max: 10, + )] + public ?int $rating = null; + + #[Assert\NotBlank( + message: 'Ðе передан Id квеÑта.', + )] + public ?int $questId = null; +} \ No newline at end of file diff --git a/app/src/Service/Dto/Classes/FilterDto.php b/app/src/Service/Dto/Classes/FilterDto.php new file mode 100644 index 0000000000000000000000000000000000000000..c74fce302b2a612025284793231da7d3a11e846f --- /dev/null +++ b/app/src/Service/Dto/Classes/FilterDto.php @@ -0,0 +1,47 @@ +<?php + +namespace App\Service\Dto\Classes; + +use App\Service\Dto\BaseDto; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'dto.filter', public: true)] +class FilterDto extends BaseDto +{ + /** + * @var string|null + */ + #[Groups(['data'])] + public ?string $search; + + /** + * @var string|null + */ + #[Groups(['data'])] + public ?string $sortField; + + /** + * @var string|null + */ + #[Groups(['data'])] + public ?string $sort; + + /** + * @var string[] + */ + #[Groups(['data'])] + public array $tags; + + /** + * @var string[] + */ + #[Groups(['data'])] + public array $genres; + + /** + * @var string[] + */ + #[Groups(['data'])] + public array $themes; +} \ No newline at end of file diff --git a/app/src/Service/Dto/Classes/IdDto.php b/app/src/Service/Dto/Classes/IdDto.php new file mode 100644 index 0000000000000000000000000000000000000000..b43229a8e3e8d4f72922eb4417539c0e1cb8dece --- /dev/null +++ b/app/src/Service/Dto/Classes/IdDto.php @@ -0,0 +1,16 @@ +<?php + +namespace App\Service\Dto\Classes; + +use App\Service\Dto\BaseDto; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Validator\Constraints as Assert; + +#[AsAlias(id: 'dto.id', public: true)] +class IdDto extends BaseDto +{ + #[Assert\NotBlank( + message: 'Ðе передан Id.', + )] + public int $id; +} \ No newline at end of file diff --git a/app/src/Service/Dto/Classes/UpdateReviewDto.php b/app/src/Service/Dto/Classes/UpdateReviewDto.php new file mode 100644 index 0000000000000000000000000000000000000000..0e372016e3b37cdaad59079447ae6d5536a1adb2 --- /dev/null +++ b/app/src/Service/Dto/Classes/UpdateReviewDto.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Service\Dto\Classes; + +use App\Service\Dto\BaseDto; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Validator\Constraints as Assert; + +#[AsAlias(id: 'dto.review.update', public: true)] +class UpdateReviewDto extends BaseDto +{ + #[Assert\NotBlank( + message: 'Ðе получен текÑÑ‚ отзыва.', + )] + public ?string $text = null; + + #[Assert\Range( + notInRangeMessage: 'Оценку можно поÑтавить от {{ min }} до {{ max }}.', + min: 0, + max: 10, + )] + public ?int $rating = null; + + #[Assert\NotBlank( + message: 'Ðе передан Id отзыва.', + )] + public ?int $reviewId = null; +} \ No newline at end of file diff --git a/app/src/Service/FilterService.php b/app/src/Service/FilterService.php new file mode 100644 index 0000000000000000000000000000000000000000..3fc5662b4fb95a569f7174f3153b5e389a0eead0 --- /dev/null +++ b/app/src/Service/FilterService.php @@ -0,0 +1,12 @@ +<?php + +namespace App\Service; + +class FilterService +{ + + public function __construct() + { + + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/Data/Filter.php b/app/src/Service/Response/Classes/Data/Filter.php new file mode 100644 index 0000000000000000000000000000000000000000..402484b9cd92b04ba97dd8c4a64b1a8f87ee80d2 --- /dev/null +++ b/app/src/Service/Response/Classes/Data/Filter.php @@ -0,0 +1,42 @@ +<?php + +namespace App\Service\Response\Classes\Data; + +use App\Entity\Genre; +use App\Entity\Tag; +use App\Entity\Theme; +use App\Repository\QuestRepository; +use Symfony\Component\Serializer\Annotation\Groups; + +class Filter +{ + /** + * @var Tag[] + */ + #[Groups(['filter'])] + public array $tags; + + /** + * @var Genre[] + */ + #[Groups(['filter'])] + public array $genres; + + /** + * @var Theme[] + */ + #[Groups(['filter'])] + public array $themes; + + /** + * @var string[] + */ + #[Groups(['filter'])] + public array $sortFields = QuestRepository::SORT_FIELDS; + + /** + * @var string[] + */ + #[Groups(['filter'])] + public array $sorts = QuestRepository::SORT_TYPES; +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/FavoritesResponse.php b/app/src/Service/Response/Classes/FavoritesResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..f153ed8e72f64f2b7a6ca44f6f31828ec9456a07 --- /dev/null +++ b/app/src/Service/Response/Classes/FavoritesResponse.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Entity\Favorite; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.favorites', public: true)] +class FavoritesResponse extends Response +{ + /** + * @var Favorite[] + */ + #[Groups(["data"])] + public array $data; + + /** + * @param Favorite[] $quest + * + * @return $this + */ + public function setData(array $quest): self + { + $this->data = $quest; + + return $this; + } + + public function getGroups(): array + { + return ['card']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/FilterParamsResponse.php b/app/src/Service/Response/Classes/FilterParamsResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..4bd7e6b4222f3cee121bf0b5f1dabc149ed2f000 --- /dev/null +++ b/app/src/Service/Response/Classes/FilterParamsResponse.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Entity\Favorite; +use App\Service\Response\Classes\Data\Filter; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.filter.params', public: true)] +class FilterParamsResponse extends Response +{ + /** + * @var Filter + */ + #[Groups(["data"])] + public Filter $data; + + public function setData(Filter $filter) + { + $this->data = $filter; + } + + public function getGroups(): array + { + return ['filter']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/FilterResponse.php b/app/src/Service/Response/Classes/FilterResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..0945d55d6ca7c381e00bd50930160c6d8ef05466 --- /dev/null +++ b/app/src/Service/Response/Classes/FilterResponse.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Service\Dto\Classes\FilterDto; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.filter', public: true)] +class FilterResponse extends Response +{ + /** + * @var FilterDto + */ + #[Groups(["data"])] + public FilterDto $data; + + /** + * @param FilterDto $quest + * + * @return $this + */ + public function setData(FilterDto $quest): self + { + $this->data = $quest; + + return $this; + } + + public function getGroups(): array + { + return ['data']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/QuestResponse.php b/app/src/Service/Response/Classes/QuestResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..bfff252b8007c9444d63f9fde5614389dd12d65a --- /dev/null +++ b/app/src/Service/Response/Classes/QuestResponse.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Entity\Quest; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.quest', public: true)] +class QuestResponse extends Response +{ + /** + * @var Quest + */ + #[Groups(["data"])] + public Quest $data; + + /** + * @param Quest $quest + * + * @return $this + */ + public function setData(Quest $quest): self + { + $this->data = $quest; + + return $this; + } + + public function getGroups(): array + { + return ['detail']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/QuestsResponse.php b/app/src/Service/Response/Classes/QuestsResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..fa03a6b5766095211a4465f9a260870a45af3c26 --- /dev/null +++ b/app/src/Service/Response/Classes/QuestsResponse.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Entity\Quest; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.quests', public: true)] +class QuestsResponse extends Response +{ + /** + * @var Quest[] + */ + #[Groups(["data"])] + public array $data; + + /** + * @param Quest[] $questList + * + * @return $this + */ + public function setData(array $questList): self + { + $this->data = $questList; + + return $this; + } + + public function getGroups(): array + { + return ['card']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/Response.php b/app/src/Service/Response/Classes/Response.php index b0f32f13cb1594628ea9188f757dbbc4a0acf0d4..28e6f51c8be36c242cbb0c95cf3aac7539ffac10 100644 --- a/app/src/Service/Response/Classes/Response.php +++ b/app/src/Service/Response/Classes/Response.php @@ -134,7 +134,7 @@ class Response implements ResponseServiceInterface $this->message = implode(', ', array_merge($this->messages, $this->errors)); - if (isset($this->data) && !empty($this->data)) { + if (isset($this->data)) { $groups = ['data']; $groups = array_merge($groups, $this->getGroups()); } diff --git a/app/src/Service/Response/Classes/ReviewsResponse.php b/app/src/Service/Response/Classes/ReviewsResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..40da2403f393f3386a1ae8e7215cd2d909efe992 --- /dev/null +++ b/app/src/Service/Response/Classes/ReviewsResponse.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Service\Response\Classes; + +use App\Entity\Review; +use Symfony\Component\DependencyInjection\Attribute\AsAlias; +use Symfony\Component\Serializer\Annotation\Groups; + +#[AsAlias(id: 'response.reviews', public: true)] +class ReviewsResponse extends Response +{ + /** + * @var Review[] + */ + #[Groups(["data"])] + public array $data; + + /** + * @param Review[] $quest + * + * @return $this + */ + public function setData(array $quest): self + { + $this->data = $quest; + + return $this; + } + + public function getGroups(): array + { + return ['card']; + } +} \ No newline at end of file diff --git a/app/symfony.lock b/app/symfony.lock index 6babe261381e9ef543850a43b480f6db498d3ca3..ceb121029f701bf0d1f19deee43a26acdbd03db3 100644 --- a/app/symfony.lock +++ b/app/symfony.lock @@ -51,6 +51,18 @@ "config/routes/nelmio_api_doc.yaml" ] }, + "nelmio/cors-bundle": { + "version": "2.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.5", + "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" + }, + "files": [ + "config/packages/nelmio_cors.yaml" + ] + }, "symfony/console": { "version": "7.0", "recipe": {