diff --git a/.gitignore b/.gitignore index ee27af29d..68d9dfc97 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ /build .php-cs-fixer.cache + +.idea/ \ No newline at end of file diff --git a/apps/mooc/backend/config/services.yaml b/apps/mooc/backend/config/services.yaml index 7d81f0953..6cbc03d90 100644 --- a/apps/mooc/backend/config/services.yaml +++ b/apps/mooc/backend/config/services.yaml @@ -96,3 +96,7 @@ services: # -- IMPLEMENTATIONS SELECTOR -- CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\WithMonitoring\WithPrometheusMonitoringEventBus' + + CodelyTv\Mooc\Notifications\Application\PublishInSocialMediaOnNewVideo: + arguments: + courseRepository: '@CodelyTv\Mooc\Courses\Infrastructure\Persistence' diff --git a/composer.json b/composer.json index ea800fcce..b47aa200f 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,10 @@ "elasticsearch/elasticsearch": "^7", "monolog/monolog": "^3", - "endclothing/prometheus_client_php": "^1" + "endclothing/prometheus_client_php": "^1", + "ext-http": "*", + "symfony/http-client": "^6.1", + "abraham/twitteroauth": "^4.0" }, "require-dev": { "ext-xdebug": "*", diff --git a/composer.lock b/composer.lock index f12724bfe..f51c5ee8e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,70 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c096ab9ab7ca0c43b0244f7dcbb55400", + "content-hash": "f57d9f235074ef77275a16ff16871cb2", "packages": [ + { + "name": "abraham/twitteroauth", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/abraham/twitteroauth.git", + "reference": "b9302599e416e5c00742cf7f4455220897f8291d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/abraham/twitteroauth/zipball/b9302599e416e5c00742cf7f4455220897f8291d", + "reference": "b9302599e416e5c00742cf7f4455220897f8291d", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.2", + "ext-curl": "*", + "php": "^7.4 || ^8.0 || ^8.1" + }, + "require-dev": { + "php-vcr/php-vcr": "^1", + "php-vcr/phpunit-testlistener-vcr": "dev-php-8", + "phpmd/phpmd": "^2", + "phpunit/phpunit": "^8 || ^9", + "rector/rector": "^0.12.19 || ^0.13.0", + "squizlabs/php_codesniffer": "^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Abraham\\TwitterOAuth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Abraham Williams", + "email": "abraham@abrah.am", + "homepage": "https://abrah.am", + "role": "Developer" + } + ], + "description": "The most popular PHP library for use with the Twitter OAuth REST API.", + "homepage": "https://twitteroauth.com", + "keywords": [ + "Twitter API", + "Twitter oAuth", + "api", + "oauth", + "rest", + "social", + "twitter" + ], + "support": { + "issues": "https://github.com/abraham/twitteroauth/issues", + "source": "https://github.com/abraham/twitteroauth" + }, + "time": "2022-08-18T23:30:33+00:00" + }, { "name": "brick/math", "version": "0.9.3", @@ -66,6 +128,82 @@ ], "time": "2021-08-15T20:50:18+00:00" }, + { + "name": "composer/ca-bundle", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "69098eca243998b53eed7a48d82dedd28b447cd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/69098eca243998b53eed7a48d82dedd28b447cd5", + "reference": "69098eca243998b53eed7a48d82dedd28b447cd5", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "psr/log": "^1.0", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.3.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-10-12T12:08:29+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -3536,6 +3674,171 @@ ], "time": "2022-06-09T10:53:06+00:00" }, + { + "name": "symfony/http-client", + "version": "v6.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "c8c887f4813370550147afd27d9eb8a8523e53b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/c8c887f4813370550147afd27d9eb8a8523e53b2", + "reference": "c8c887f4813370550147afd27d9eb8a8523e53b2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.1.6" + }, + "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": "2022-10-12T05:10:31+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "fd038f08c623ab5d22b26e9ba35afe8c79071800" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/fd038f08c623ab5d22b26e9ba35afe8c79071800", + "reference": "fd038f08c623ab5d22b26e9ba35afe8c79071800", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.1.1" + }, + "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": "2022-04-22T07:30:54+00:00" + }, { "name": "symfony/http-foundation", "version": "v6.1.1", @@ -10308,7 +10611,8 @@ "ext-apcu": "*", "ext-json": "*", "ext-zend-opcache": "*", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-http": "*" }, "platform-dev": { "ext-xdebug": "*" diff --git a/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php b/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php index 9fb967455..d14b79bfc 100644 --- a/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php +++ b/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php @@ -6,6 +6,9 @@ use CodelyTv\Backoffice\Courses\Domain\BackofficeCourse; use CodelyTv\Backoffice\Courses\Domain\BackofficeCourseRepository; +use CodelyTv\Mooc\Courses\Domain\CourseDuration; +use CodelyTv\Mooc\Courses\Domain\CourseName; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; final class BackofficeCourseCreator { @@ -13,8 +16,8 @@ public function __construct(private readonly BackofficeCourseRepository $reposit { } - public function create(string $id, string $name, string $duration): void + public function create(CourseId $id, CourseName $name, CourseDuration $duration): void { - $this->repository->save(new BackofficeCourse($id, $name, $duration)); + $this->repository->save(BackofficeCourse::create($id, $name, $duration)); } } diff --git a/src/Backoffice/Courses/Domain/BackofficeCourse.php b/src/Backoffice/Courses/Domain/BackofficeCourse.php index 96636c2d0..141fcc4e0 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourse.php +++ b/src/Backoffice/Courses/Domain/BackofficeCourse.php @@ -4,12 +4,31 @@ namespace CodelyTv\Backoffice\Courses\Domain; +use CodelyTv\Mooc\Courses\Domain\CourseDuration; +use CodelyTv\Mooc\Courses\Domain\CourseName; +use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Aggregate\AggregateRoot; final class BackofficeCourse extends AggregateRoot { - public function __construct(private readonly string $id, private readonly string $name, private readonly string $duration) + private CourseId $id; + private CourseName $name; + private CourseDuration $duration; + + public function __construct(CourseId $id, CourseName $name, CourseDuration $duration) + { + $this->id = $id; + $this->name = $name; + $this->duration = $duration; + } + + public static function create( + CourseId $id, + CourseName $name, + CourseDuration $duration, + ): BackofficeCourse { + return new self($id, $name, $duration); } public static function fromPrimitives(array $primitives): BackofficeCourse diff --git a/src/Mooc/Courses/Application/Find/CourseFinder.php b/src/Mooc/Courses/Application/Find/CourseFinder.php index b307b9d37..5443acf95 100644 --- a/src/Mooc/Courses/Application/Find/CourseFinder.php +++ b/src/Mooc/Courses/Application/Find/CourseFinder.php @@ -8,16 +8,20 @@ use CodelyTv\Mooc\Courses\Domain\CourseNotExist; use CodelyTv\Mooc\Courses\Domain\CourseRepository; use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; +use CodelyTv\Mooc\Courses\Domain\CourseFinder as DomainCourseFinder; final class CourseFinder { - public function __construct(private readonly CourseRepository $repository) + private DomainCourseFinder $finder; + + public function __construct(CourseRepository $repository) { + $this->finder = new DomainCourseFinder($repository); } public function __invoke(CourseId $id): Course { - $course = $this->repository->search($id); + $course = $this->finder->__invoke($id); if (null === $course) { throw new CourseNotExist($id); diff --git a/src/Mooc/Courses/Application/Update/CourseRenamer.php b/src/Mooc/Courses/Application/Update/CourseRenamer.php index e1916c4f4..ed652cf68 100644 --- a/src/Mooc/Courses/Application/Update/CourseRenamer.php +++ b/src/Mooc/Courses/Application/Update/CourseRenamer.php @@ -9,14 +9,15 @@ use CodelyTv\Mooc\Courses\Domain\CourseRepository; use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Shared\Domain\Bus\Event\EventBus; +use \CodelyTv\Mooc\Courses\Domain\CourseFinder as DomainCourseFinder; final class CourseRenamer { - private readonly CourseFinder $finder; + private readonly DomainCourseFinder $finder; public function __construct(private readonly CourseRepository $repository, private readonly EventBus $bus) { - $this->finder = new CourseFinder($repository); + $this->finder = new DomainCourseFinder($repository); } public function __invoke(CourseId $id, CourseName $newName): void diff --git a/src/Mooc/Courses/Domain/Course.php b/src/Mooc/Courses/Domain/Course.php index f41041490..024368ad0 100644 --- a/src/Mooc/Courses/Domain/Course.php +++ b/src/Mooc/Courses/Domain/Course.php @@ -9,7 +9,7 @@ final class Course extends AggregateRoot { - public function __construct(private readonly CourseId $id, private CourseName $name, private readonly CourseDuration $duration) + public function __construct(private readonly CourseId $id, public CourseName $name, private readonly CourseDuration $duration) { } diff --git a/src/Mooc/Courses/Domain/CourseFinder.php b/src/Mooc/Courses/Domain/CourseFinder.php new file mode 100644 index 000000000..e863dfa6d --- /dev/null +++ b/src/Mooc/Courses/Domain/CourseFinder.php @@ -0,0 +1,30 @@ +courseRepository = $courseRepository; + } + + /** + * @param CourseId $id + * @return Course|null + */ + public function __invoke(CourseId $id): ?Course + { + $course = $this->courseRepository->search($id); + + if (null === $course) { + throw new CourseNotExist($id); + } + + return $course; + } +} \ No newline at end of file diff --git a/src/Mooc/Notifications/Application/PublishInSocialMediaOnNewVideo/PublishInSocialMediaOnNewVideo.php b/src/Mooc/Notifications/Application/PublishInSocialMediaOnNewVideo/PublishInSocialMediaOnNewVideo.php new file mode 100644 index 000000000..f545ba1fd --- /dev/null +++ b/src/Mooc/Notifications/Application/PublishInSocialMediaOnNewVideo/PublishInSocialMediaOnNewVideo.php @@ -0,0 +1,35 @@ +socialMediaRepository->newPost($post); + } +} diff --git a/src/Mooc/Notifications/Domain/SocialMediaPost.php b/src/Mooc/Notifications/Domain/SocialMediaPost.php new file mode 100644 index 000000000..3dc4a9a4e --- /dev/null +++ b/src/Mooc/Notifications/Domain/SocialMediaPost.php @@ -0,0 +1,56 @@ +text = $text; + } + + public function getText(): string + { + return $this->text; + } + + public static function create( + VideoType $type, + VideoTitle $title, + VideoUrl $url, + CourseId $courseId, + CourseRepository $courseRepository, + ):SocialMediaPost { + + switch ($type) { + case VideoType::INTERVIEW: + $typeText = self::TEXT_VIDEO_TYPE_INTERVIEW; + break; + default: + $typeText = self::TEXT_VIDEO_TYPE_DEFAULT; + } + + /** @var Course $course */ + $course = $courseRepository->search($courseId); + + $text = '¡Hemos publicado ' . $typeText . '! Puedes encontrar ' . $title->value() . ', correspondiente al curso ' . $course->name->value() . ', aquí: ' . $url->value(); + + return new self($type, $title, $url, $courseId, $text); + } +} \ No newline at end of file diff --git a/src/Mooc/Notifications/Domain/SocialMediaRepository.php b/src/Mooc/Notifications/Domain/SocialMediaRepository.php new file mode 100644 index 000000000..6e0d3abc0 --- /dev/null +++ b/src/Mooc/Notifications/Domain/SocialMediaRepository.php @@ -0,0 +1,12 @@ +twitterOauthConnection = new TwitterOAuth( + '1zRrpOlHQtX8hfffC927VJe2212fAAnC', + 'zTYliSpcBffffTnktDxlWt5kcaA6k8Ov8McN16U2oNxc8CdKcG41231IkuU0N', + '27z80514284-cpO3a2R9Ta8ZkfffplFEYik8w7i2jTJw123bnVipjkf6Z', + 'azc91fffA1yYXA4fru9jT6RgoI4gTg0O312y6iD17Lf4IfzwH03C', + ); + + $this->twitterOauthConnection->setApiVersion('2'); + + $content = $this->twitterOauthConnection->get("account/verify_credentials"); + } + + public function newPost(SocialMediaPost $socialMediaPost): array|object + { + return $this->twitterOauthConnection->post("statuses/update", ["status" => $socialMediaPost->getText()]); + } +} \ No newline at end of file diff --git a/src/Mooc/Videos/Application/Create/VideoCreator.php b/src/Mooc/Videos/Application/Create/VideoCreator.php index 91180306b..42c4aef9d 100644 --- a/src/Mooc/Videos/Application/Create/VideoCreator.php +++ b/src/Mooc/Videos/Application/Create/VideoCreator.php @@ -4,6 +4,9 @@ namespace CodelyTv\Mooc\Videos\Application\Create; +use CodelyTv\Mooc\Courses\Domain\CourseRepository; +use CodelyTv\Mooc\Notifications\Domain\SocialMediaPost; +use CodelyTv\Mooc\Notifications\Domain\SocialMediaRepository; use CodelyTv\Mooc\Shared\Domain\Courses\CourseId; use CodelyTv\Mooc\Shared\Domain\Videos\VideoUrl; use CodelyTv\Mooc\Videos\Domain\Video; @@ -15,7 +18,13 @@ final class VideoCreator { - public function __construct(private readonly VideoRepository $repository, private readonly EventBus $bus) + public function __construct( + private readonly VideoRepository $repository, + private readonly EventBus $bus, + private readonly SocialMediaRepository $socialMediaRepository, + private readonly SocialMediaPost $socialMediaPost, + private readonly CourseRepository $courseRepository, + ) { } @@ -25,6 +34,9 @@ public function create(VideoId $id, VideoType $type, VideoTitle $title, VideoUrl $this->repository->save($video); + $post = $this->socialMediaPost->create($type, $title, $url, $courseId, $this->courseRepository); + $this->socialMediaRepository->newPost($post); + $this->bus->publish(...$video->pullDomainEvents()); } }