[backend] accept articles from crawler
This commit is contained in:
@@ -19,10 +19,11 @@ APP_ENV=dev
|
|||||||
APP_SECRET=f51bc23b4d778b25fcb9804bb9dfaf39
|
APP_SECRET=f51bc23b4d778b25fcb9804bb9dfaf39
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
CRAWLING_NOTIFICATION_EMAIL=
|
BASANGO_CRAWLER_TOKEN=dev
|
||||||
TIMEZONE=Africa/Lubumbashi
|
BASANGO_NOTIFICATION_EMAIL=
|
||||||
GOOGLE_CUSTOM_SEARCH_API_KEY=
|
BASANGO_TIMEZONE=Africa/Lubumbashi
|
||||||
NEWS_DATA_API_KEY=
|
BASANGO_GOOGLE_SEARCH_API_KEY=
|
||||||
|
BASANGO_NEWS_DATA_API_KEY=
|
||||||
|
|
||||||
###> Devy ###
|
###> Devy ###
|
||||||
DEVY_TOKEN=
|
DEVY_TOKEN=
|
||||||
@@ -47,7 +48,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
|||||||
#
|
#
|
||||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
DATABASE_URL="mysql://root:root@database:3306/app?serverVersion=Mariadb-10.11.11&charset=utf8mb4"
|
DATABASE_URL="mysql://root:root@mariadb:3306/app?serverVersion=Mariadb-10.11.11&charset=utf8mb4"
|
||||||
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# define your env variables for the test env here
|
# define your env variables for the test env here
|
||||||
KERNEL_CLASS='App\SharedKernel\Infrastructure\Framework\Symfony\Kernel'
|
KERNEL_CLASS='Basango\SharedKernel\Infrastructure\Framework\Symfony\Kernel'
|
||||||
APP_SECRET='$ecretf0rt3st'
|
APP_SECRET='$ecretf0rt3st'
|
||||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||||
PANTHER_APP_ENV=panther
|
PANTHER_APP_ENV=panther
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
meta {
|
||||||
|
name: add-article
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/aggregator/articles?token=dev
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
token: dev
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"body": "hello world",
|
||||||
|
"link": "https://devscast.tech",
|
||||||
|
"categories": ["a", "b"],
|
||||||
|
"source": "radiookapi.net",
|
||||||
|
"timestamp": 12,
|
||||||
|
"metadata": {
|
||||||
|
"title": "test",
|
||||||
|
"description": "some description",
|
||||||
|
"image": "https://devscast.tech/logo.svg",
|
||||||
|
"locale": "fr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: aggregator
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
@@ -13,4 +13,4 @@ default:
|
|||||||
FriendsOfBehat\SymfonyExtension:
|
FriendsOfBehat\SymfonyExtension:
|
||||||
bootstrap: tests/bootstrap.php
|
bootstrap: tests/bootstrap.php
|
||||||
kernel:
|
kernel:
|
||||||
class: App\SharedKernel\Infrastructure\Framework\Symfony\Kernel
|
class: Basango\SharedKernel\Infrastructure\Framework\Symfony\Kernel
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
use App\SharedKernel\Infrastructure\Framework\Symfony\Kernel;
|
use Basango\SharedKernel\Infrastructure\Framework\Symfony\Kernel;
|
||||||
|
|
||||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "src/"
|
"Basango\\": "src/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\Aggregator\Domain\Model\Entity\Article"
|
name="Basango\Aggregator\Domain\Model\Entity\Article"
|
||||||
repository-class="App\Aggregator\Infrastructure\Persistence\Doctrine\ORM\ArticleOrmRepository"
|
repository-class="Basango\Aggregator\Infrastructure\Persistence\Doctrine\ORM\ArticleOrmRepository"
|
||||||
table="article"
|
table="article"
|
||||||
>
|
>
|
||||||
<id name="id" type="article_id">
|
<id name="id" type="article_id">
|
||||||
@@ -19,22 +19,22 @@
|
|||||||
|
|
||||||
<field name="title" length="1024" />
|
<field name="title" length="1024" />
|
||||||
<field name="body" type="text" />
|
<field name="body" type="text" />
|
||||||
<embedded name="link" class="App\Aggregator\Domain\Model\ValueObject\Link" use-column-prefix="false" />
|
<embedded name="link" class="Basango\Aggregator\Domain\Model\ValueObject\Link" use-column-prefix="false" />
|
||||||
<field name="hash" length="32" />
|
<field name="hash" length="32" />
|
||||||
<field name="categories" nullable="true" />
|
<field name="categories" nullable="true" />
|
||||||
|
|
||||||
<many-to-one field="source" target-entity="App\Aggregator\Domain\Model\Entity\Source">
|
<many-to-one field="source" target-entity="Basango\Aggregator\Domain\Model\Entity\Source">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
<embedded name="credibility" class="App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility" use-column-prefix="false" />
|
<embedded name="credibility" class="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility" use-column-prefix="false" />
|
||||||
<field name="sentiment" enum-type="App\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment" length="30">
|
<field name="sentiment" enum-type="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment" length="30">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">neutral</option>
|
<option name="default">neutral</option>
|
||||||
</options>
|
</options>
|
||||||
</field>
|
</field>
|
||||||
<field name="metadata" type="open_graph" nullable="true" />
|
<field name="metadata" type="open_graph" nullable="true" />
|
||||||
<embedded name="readingTime" class="App\Aggregator\Domain\Model\ValueObject\ReadingTime" use-column-prefix="false" />
|
<embedded name="readingTime" class="Basango\Aggregator\Domain\Model\ValueObject\ReadingTime" use-column-prefix="false" />
|
||||||
|
|
||||||
<field name="image"
|
<field name="image"
|
||||||
insertable="false"
|
insertable="false"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\Aggregator\Domain\Model\Entity\Source"
|
name="Basango\Aggregator\Domain\Model\Entity\Source"
|
||||||
repository-class="App\Aggregator\Infrastructure\Persistence\Doctrine\ORM\SourceOrmRepository"
|
repository-class="Basango\Aggregator\Infrastructure\Persistence\Doctrine\ORM\SourceOrmRepository"
|
||||||
table="source"
|
table="source"
|
||||||
>
|
>
|
||||||
<id name="id" type="source_id">
|
<id name="id" type="source_id">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<field name="url" />
|
<field name="url" />
|
||||||
<field name="name" unique="true" />
|
<field name="name" unique="true" />
|
||||||
|
|
||||||
<embedded name="credibility" class="App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility" use-column-prefix="false" />
|
<embedded name="credibility" class="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility" use-column-prefix="false" />
|
||||||
<field name="displayName" nullable="true" />
|
<field name="displayName" nullable="true" />
|
||||||
<field name="description" length="1024" nullable="true" />
|
<field name="description" length="1024" nullable="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\Aggregator\Domain\Model\ValueObject\Link">
|
<embeddable name="Basango\Aggregator\Domain\Model\ValueObject\Link">
|
||||||
<field name="link" length="1024" />
|
<field name="link" length="1024" />
|
||||||
</embeddable>
|
</embeddable>
|
||||||
</doctrine-mapping>
|
</doctrine-mapping>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\Aggregator\Domain\Model\ValueObject\ReadingTime">
|
<embeddable name="Basango\Aggregator\Domain\Model\ValueObject\ReadingTime">
|
||||||
<field name="readingTime" type="integer" nullable="true">
|
<field name="readingTime" type="integer" nullable="true">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">1</option>
|
<option name="default">1</option>
|
||||||
|
|||||||
@@ -4,18 +4,18 @@
|
|||||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility">
|
<embeddable name="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility">
|
||||||
<field name="bias" enum-type="App\Aggregator\Domain\Model\ValueObject\Scoring\Bias" length="30">
|
<field name="bias" enum-type="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Bias" length="30">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">neutral</option>
|
<option name="default">neutral</option>
|
||||||
</options>
|
</options>
|
||||||
</field>
|
</field>
|
||||||
<field name="reliability" enum-type="App\Aggregator\Domain\Model\ValueObject\Scoring\Reliability" length="30">
|
<field name="reliability" enum-type="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Reliability" length="30">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">reliable</option>
|
<option name="default">reliable</option>
|
||||||
</options>
|
</options>
|
||||||
</field>
|
</field>
|
||||||
<field name="transparency" enum-type="App\Aggregator\Domain\Model\ValueObject\Scoring\Transparency" length="30">
|
<field name="transparency" enum-type="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Transparency" length="30">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">medium</option>
|
<option name="default">medium</option>
|
||||||
</options>
|
</options>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\FeedManagement\Domain\Model\Entity\Bookmark"
|
name="Basango\FeedManagement\Domain\Model\Entity\Bookmark"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\BookmarkOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\BookmarkOrmRepository"
|
||||||
table="bookmark"
|
table="bookmark"
|
||||||
>
|
>
|
||||||
<id name="id" type="bookmark_id">
|
<id name="id" type="bookmark_id">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</id>
|
</id>
|
||||||
|
|
||||||
<!-- fetching eager cause will always need to check the user's id whenever we deal with a bookmark -->
|
<!-- fetching eager cause will always need to check the user's id whenever we deal with a bookmark -->
|
||||||
<many-to-one field="user" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User" fetch="EAGER">
|
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User" fetch="EAGER">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</options>
|
</options>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
<many-to-many field="articles" target-entity="App\Aggregator\Domain\Model\Entity\Article">
|
<many-to-many field="articles" target-entity="Basango\Aggregator\Domain\Model\Entity\Article">
|
||||||
<join-table name="bookmark_article">
|
<join-table name="bookmark_article">
|
||||||
<join-columns>
|
<join-columns>
|
||||||
<join-column name="bookmark_id" referenced-column-name="id" on-delete="CASCADE" />
|
<join-column name="bookmark_id" referenced-column-name="id" on-delete="CASCADE" />
|
||||||
|
|||||||
@@ -4,23 +4,23 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\FeedManagement\Domain\Model\Entity\Comment"
|
name="Basango\FeedManagement\Domain\Model\Entity\Comment"
|
||||||
repository-class="App\FeedManagement\Infrastructure\Persistence\Doctrine\ORM\CommentOrmRepository"
|
repository-class="Basango\FeedManagement\Infrastructure\Persistence\Doctrine\ORM\CommentOrmRepository"
|
||||||
table="comment"
|
table="comment"
|
||||||
>
|
>
|
||||||
<id name="id" type="comment_id">
|
<id name="id" type="comment_id">
|
||||||
<generator strategy="NONE"/>
|
<generator strategy="NONE"/>
|
||||||
</id>
|
</id>
|
||||||
|
|
||||||
<many-to-one field="user" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User">
|
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
<many-to-one field="article" target-entity="App\Aggregator\Domain\Model\Entity\Article">
|
<many-to-one field="article" target-entity="Basango\Aggregator\Domain\Model\Entity\Article">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
<field name="content" length="512" />
|
<field name="content" length="512" />
|
||||||
<field name="sentiment" enum-type="App\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment" length="30">
|
<field name="sentiment" enum-type="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment" length="30">
|
||||||
<options>
|
<options>
|
||||||
<option name="default">neutral</option>
|
<option name="default">neutral</option>
|
||||||
</options>
|
</options>
|
||||||
|
|||||||
@@ -4,18 +4,18 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\FeedManagement\Domain\Model\Entity\FollowedSource"
|
name="Basango\FeedManagement\Domain\Model\Entity\FollowedSource"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\FollowedSourceOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\FollowedSourceOrmRepository"
|
||||||
table="followed_source"
|
table="followed_source"
|
||||||
>
|
>
|
||||||
<id name="id" type="followed_source_id">
|
<id name="id" type="followed_source_id">
|
||||||
<generator strategy="NONE"/>
|
<generator strategy="NONE"/>
|
||||||
</id>
|
</id>
|
||||||
|
|
||||||
<many-to-one field="follower" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User">
|
<many-to-one field="follower" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
<many-to-one field="source" target-entity="App\Aggregator\Domain\Model\Entity\Source">
|
<many-to-one field="source" target-entity="Basango\Aggregator\Domain\Model\Entity\Source">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\IdentityAndAccess\Domain\Model\Entity\LoginAttempt"
|
name="Basango\IdentityAndAccess\Domain\Model\Entity\LoginAttempt"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\LoginAttemptOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\LoginAttemptOrmRepository"
|
||||||
table="login_attempt"
|
table="login_attempt"
|
||||||
>
|
>
|
||||||
<id name="id" type="login_attempt_id">
|
<id name="id" type="login_attempt_id">
|
||||||
<generator strategy="NONE"/>
|
<generator strategy="NONE"/>
|
||||||
</id>
|
</id>
|
||||||
|
|
||||||
<many-to-one field="user" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User">
|
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\IdentityAndAccess\Domain\Model\Entity\LoginHistory"
|
name="Basango\IdentityAndAccess\Domain\Model\Entity\LoginHistory"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\LoginAttemptOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\LoginAttemptOrmRepository"
|
||||||
table="login_history"
|
table="login_history"
|
||||||
>
|
>
|
||||||
<id name="id" type="login_history_id">
|
<id name="id" type="login_history_id">
|
||||||
<generator strategy="NONE"/>
|
<generator strategy="NONE"/>
|
||||||
</id>
|
</id>
|
||||||
|
|
||||||
<many-to-one field="user" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User">
|
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
|
|
||||||
<field name="ipAddress" nullable="true" length="15" />
|
<field name="ipAddress" nullable="true" length="15" />
|
||||||
<embedded name="device" class="App\SharedKernel\Domain\Model\ValueObject\Tracking\Device" />
|
<embedded name="device" class="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\Device" />
|
||||||
<embedded name="location" class="App\SharedKernel\Domain\Model\ValueObject\Tracking\GeoLocation" />
|
<embedded name="location" class="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\GeoLocation" />
|
||||||
|
|
||||||
|
|
||||||
<field name="createdAt" type="datetime_immutable"/>
|
<field name="createdAt" type="datetime_immutable"/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\IdentityAndAccess\Domain\Model\Entity\RefreshToken"
|
name="Basango\IdentityAndAccess\Domain\Model\Entity\RefreshToken"
|
||||||
repository-class="Gesdinet\JWTRefreshTokenBundle\Entity\RefreshTokenRepository"
|
repository-class="Gesdinet\JWTRefreshTokenBundle\Entity\RefreshTokenRepository"
|
||||||
table="refresh_tokens"
|
table="refresh_tokens"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\IdentityAndAccess\Domain\Model\Entity\User"
|
name="Basango\IdentityAndAccess\Domain\Model\Entity\User"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\UserOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\UserOrmRepository"
|
||||||
table="user"
|
table="user"
|
||||||
>
|
>
|
||||||
<id name="id" type="user_id">
|
<id name="id" type="user_id">
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</options>
|
</options>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
<embedded name="roles" class="App\IdentityAndAccess\Domain\Model\ValueObject\Roles" use-column-prefix="false" />
|
<embedded name="roles" class="Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles" use-column-prefix="false" />
|
||||||
|
|
||||||
<field name="createdAt" type="datetime_immutable" />
|
<field name="createdAt" type="datetime_immutable" />
|
||||||
<field name="updatedAt" type="datetime_immutable" nullable="true" />
|
<field name="updatedAt" type="datetime_immutable" nullable="true" />
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
<entity
|
<entity
|
||||||
name="App\IdentityAndAccess\Domain\Model\Entity\VerificationToken"
|
name="Basango\IdentityAndAccess\Domain\Model\Entity\VerificationToken"
|
||||||
repository-class="App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\VerificationTokenOrmRepository"
|
repository-class="Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\ORM\VerificationTokenOrmRepository"
|
||||||
table="verification_token"
|
table="verification_token"
|
||||||
>
|
>
|
||||||
<id name="id" type="verification_token_id">
|
<id name="id" type="verification_token_id">
|
||||||
<generator strategy="NONE"/>
|
<generator strategy="NONE"/>
|
||||||
</id>
|
</id>
|
||||||
|
|
||||||
<many-to-one field="user" target-entity="App\IdentityAndAccess\Domain\Model\Entity\User" fetch="EAGER">
|
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User" fetch="EAGER">
|
||||||
<join-column nullable="false" on-delete="CASCADE" />
|
<join-column nullable="false" on-delete="CASCADE" />
|
||||||
</many-to-one>
|
</many-to-one>
|
||||||
<field name="purpose" enum-type="App\IdentityAndAccess\Domain\Model\ValueObject\Secret\TokenPurpose" />
|
<field name="purpose" enum-type="Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\TokenPurpose" />
|
||||||
<embedded name="token" class="App\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken" use-column-prefix="false" />
|
<embedded name="token" class="Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken" use-column-prefix="false" />
|
||||||
|
|
||||||
<field name="createdAt" type="datetime_immutable"/>
|
<field name="createdAt" type="datetime_immutable"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\IdentityAndAccess\Domain\Model\ValueObject\Roles">
|
<embeddable name="Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles">
|
||||||
<field name="roles" type="json"/>
|
<field name="roles" type="json"/>
|
||||||
</embeddable>
|
</embeddable>
|
||||||
</doctrine-mapping>
|
</doctrine-mapping>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken">
|
<embeddable name="Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken">
|
||||||
<field name="token" length="60" nullable="true" />
|
<field name="token" length="60" nullable="true" />
|
||||||
</embeddable>
|
</embeddable>
|
||||||
</doctrine-mapping>
|
</doctrine-mapping>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\SharedKernel\Domain\Model\ValueObject\Tracking\Device">
|
<embeddable name="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\Device">
|
||||||
<field name="operatingSystem" type="string" nullable="true" />
|
<field name="operatingSystem" type="string" nullable="true" />
|
||||||
<field name="client" type="string" nullable="true" />
|
<field name="client" type="string" nullable="true" />
|
||||||
<field name="device" type="string" nullable="true" />
|
<field name="device" type="string" nullable="true" />
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
<embeddable name="App\SharedKernel\Domain\Model\ValueObject\Tracking\GeoLocation">
|
<embeddable name="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\GeoLocation">
|
||||||
<field name="timeZone" type="string" nullable="true"/>
|
<field name="timeZone" type="string" nullable="true"/>
|
||||||
<field name="longitude" type="float" nullable="true"/>
|
<field name="longitude" type="float" nullable="true"/>
|
||||||
<field name="latitude" type="float" nullable="true" />
|
<field name="latitude" type="float" nullable="true" />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace DoctrineMigrations;
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\SourceId;
|
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
|||||||
@@ -11,23 +11,23 @@ doctrine:
|
|||||||
result_cache: 'cache.dbal'
|
result_cache: 'cache.dbal'
|
||||||
types:
|
types:
|
||||||
# Shared Kernel
|
# Shared Kernel
|
||||||
email: App\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Types\EmailType
|
email: Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Types\EmailType
|
||||||
|
|
||||||
# Aggregator
|
# Aggregator
|
||||||
article_id: App\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\ArticleIdType
|
article_id: Basango\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\ArticleIdType
|
||||||
source_id: App\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\SourceIdType
|
source_id: Basango\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\SourceIdType
|
||||||
open_graph: App\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\OpenGraphType
|
open_graph: Basango\Aggregator\Infrastructure\Persistence\Doctrine\DBAL\Types\OpenGraphType
|
||||||
|
|
||||||
# Identity and Access
|
# Identity and Access
|
||||||
user_id: App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\UserIdType
|
user_id: Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\UserIdType
|
||||||
login_attempt_id: App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\LoginAttemptIdType
|
login_attempt_id: Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\LoginAttemptIdType
|
||||||
login_history_id: App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\LoginHistoryIdType
|
login_history_id: Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\LoginHistoryIdType
|
||||||
verification_token_id: App\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\VerificationTokenIdType
|
verification_token_id: Basango\IdentityAndAccess\Infrastructure\Persistence\Doctrine\DBAL\Types\VerificationTokenIdType
|
||||||
|
|
||||||
# FeedManagement
|
# FeedManagement
|
||||||
bookmark_id: App\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\BookmarkIdType
|
bookmark_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\BookmarkIdType
|
||||||
followed_source_id: App\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\FollowedSourceIdType
|
followed_source_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\FollowedSourceIdType
|
||||||
comment_id: App\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\CommentIdType
|
comment_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\CommentIdType
|
||||||
orm:
|
orm:
|
||||||
auto_generate_proxy_classes: true
|
auto_generate_proxy_classes: true
|
||||||
enable_lazy_ghost_objects: true
|
enable_lazy_ghost_objects: true
|
||||||
@@ -40,22 +40,22 @@ doctrine:
|
|||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: xml
|
type: xml
|
||||||
dir: '%kernel.project_dir%/config/doctrine/Aggregator'
|
dir: '%kernel.project_dir%/config/doctrine/Aggregator'
|
||||||
prefix: 'App\Aggregator\Domain\Model'
|
prefix: 'Basango\Aggregator\Domain\Model'
|
||||||
IdentityAndAccess:
|
IdentityAndAccess:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: xml
|
type: xml
|
||||||
dir: '%kernel.project_dir%/config/doctrine/IdentityAndAccess'
|
dir: '%kernel.project_dir%/config/doctrine/IdentityAndAccess'
|
||||||
prefix: 'App\IdentityAndAccess\Domain\Model'
|
prefix: 'Basango\IdentityAndAccess\Domain\Model'
|
||||||
FeedManagement:
|
FeedManagement:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: xml
|
type: xml
|
||||||
dir: '%kernel.project_dir%/config/doctrine/FeedManagement'
|
dir: '%kernel.project_dir%/config/doctrine/FeedManagement'
|
||||||
prefix: 'App\FeedManagement\Domain\Model'
|
prefix: 'Basango\FeedManagement\Domain\Model'
|
||||||
SharedKernel:
|
SharedKernel:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: xml
|
type: xml
|
||||||
dir: '%kernel.project_dir%/config/doctrine/SharedKernel'
|
dir: '%kernel.project_dir%/config/doctrine/SharedKernel'
|
||||||
prefix: 'App\SharedKernel\Domain\Model'
|
prefix: 'Basango\SharedKernel\Domain\Model'
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: false
|
auto_mapping: false
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
doctrine_migrations:
|
doctrine_migrations:
|
||||||
migrations_paths:
|
migrations_paths:
|
||||||
# namespace is arbitrary but should be different from App\Migrations
|
# namespace is arbitrary but should be different from Basango\Migrations
|
||||||
# as migrations classes should NOT be autoloaded
|
# as migrations classes should NOT be autoloaded
|
||||||
'DoctrineMigrations': '%kernel.project_dir%/config/migrations'
|
'DoctrineMigrations': '%kernel.project_dir%/config/migrations'
|
||||||
enable_profiler: false
|
enable_profiler: false
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
gesdinet_jwt_refresh_token:
|
gesdinet_jwt_refresh_token:
|
||||||
refresh_token_class: App\IdentityAndAccess\Domain\Model\Entity\RefreshToken
|
refresh_token_class: Basango\IdentityAndAccess\Domain\Model\Entity\RefreshToken
|
||||||
ttl: 2592000 # 1 month
|
ttl: 2592000 # 1 month
|
||||||
ttl_update: true
|
ttl_update: true
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ framework:
|
|||||||
sync: 'sync://'
|
sync: 'sync://'
|
||||||
|
|
||||||
routing:
|
routing:
|
||||||
App\SharedKernel\Application\Messaging\AsyncMessage: async
|
Basango\SharedKernel\Application\Messaging\AsyncMessage: async
|
||||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
||||||
|
|
||||||
default_bus: command.bus
|
default_bus: command.bus
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ when@prod:
|
|||||||
disable_webpage_preview: true
|
disable_webpage_preview: true
|
||||||
disable_notification: false
|
disable_notification: false
|
||||||
split_long_messages: false
|
split_long_messages: false
|
||||||
formatter: App\SharedKernel\Infrastructure\Framework\Symfony\Logging\TelegramFormatter
|
formatter: Basango\SharedKernel\Infrastructure\Framework\Symfony\Logging\TelegramFormatter
|
||||||
nested:
|
nested:
|
||||||
type: rotating_file
|
type: rotating_file
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ security:
|
|||||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||||
providers:
|
providers:
|
||||||
app_user_provider:
|
app_user_provider:
|
||||||
id: App\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\SecurityUserProvider
|
id: Basango\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\SecurityUserProvider
|
||||||
|
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
@@ -19,7 +19,7 @@ security:
|
|||||||
check_path: /api/login_check
|
check_path: /api/login_check
|
||||||
success_handler: lexik_jwt_authentication.handler.authentication_success
|
success_handler: lexik_jwt_authentication.handler.authentication_success
|
||||||
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
||||||
user_checker: App\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
user_checker: Basango\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
||||||
|
|
||||||
api:
|
api:
|
||||||
pattern: ^/api
|
pattern: ^/api
|
||||||
@@ -30,12 +30,12 @@ security:
|
|||||||
check_path: /api/token/refresh
|
check_path: /api/token/refresh
|
||||||
logout:
|
logout:
|
||||||
path: api_token_invalidate
|
path: api_token_invalidate
|
||||||
user_checker: App\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
user_checker: Basango\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
||||||
|
|
||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
user_checker: App\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
user_checker: Basango\IdentityAndAccess\Infrastructure\Framework\Symfony\Security\UserChecker
|
||||||
|
|
||||||
# Easy way to control access for large sections of your site
|
# Easy way to control access for large sections of your site
|
||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
@@ -45,6 +45,7 @@ security:
|
|||||||
- { path: ^/api/token/refresh, roles: PUBLIC_ACCESS }
|
- { path: ^/api/token/refresh, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/password/(request|reset), roles: PUBLIC_ACCESS }
|
- { path: ^/api/password/(request|reset), roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/account/(unlock|confirm), roles: PUBLIC_ACCESS }
|
- { path: ^/api/account/(unlock|confirm), roles: PUBLIC_ACCESS }
|
||||||
|
- { path: ^/api/aggregator/articles, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ twig:
|
|||||||
decimal_point: ','
|
decimal_point: ','
|
||||||
thousands_separator: '.'
|
thousands_separator: '.'
|
||||||
globals:
|
globals:
|
||||||
'application': '@App\SharedKernel\Domain\Application'
|
'application': '@Basango\SharedKernel\Domain\Application'
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
twig:
|
twig:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
parameters:
|
parameters:
|
||||||
app_crawling_notification_email: '%env(CRAWLING_NOTIFICATION_EMAIL)%'
|
basango_notification_email: '%env(BASANGO_NOTIFICATION_EMAIL)%'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
@@ -13,19 +13,19 @@ services:
|
|||||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
bind:
|
bind:
|
||||||
$projectDir: '%kernel.project_dir%'
|
$projectDir: '%kernel.project_dir%'
|
||||||
$crawlingNotificationEmail: '%app_crawling_notification_email%'
|
$crawlingNotificationEmail: '%basango_notification_email%'
|
||||||
|
|
||||||
_instanceof:
|
_instanceof:
|
||||||
App\SharedKernel\Application\Messaging\CommandHandler:
|
Basango\SharedKernel\Application\Messaging\CommandHandler:
|
||||||
tags:
|
tags:
|
||||||
- { name: messenger.message_handler, bus: 'command.bus' }
|
- { name: messenger.message_handler, bus: 'command.bus' }
|
||||||
App\SharedKernel\Application\Messaging\QueryHandler:
|
Basango\SharedKernel\Application\Messaging\QueryHandler:
|
||||||
tags:
|
tags:
|
||||||
- { name: messenger.message_handler, bus: 'query.bus' }
|
- { name: messenger.message_handler, bus: 'query.bus' }
|
||||||
App\SharedKernel\Application\Messaging\MessageHandler:
|
Basango\SharedKernel\Application\Messaging\MessageHandler:
|
||||||
tags:
|
tags:
|
||||||
- { name: messenger.message_handler, bus: 'message.bus' }
|
- { name: messenger.message_handler, bus: 'message.bus' }
|
||||||
App\SharedKernel\Domain\EventListener\EventListener:
|
Basango\SharedKernel\Domain\EventListener\EventListener:
|
||||||
tags:
|
tags:
|
||||||
- { name: kernel.event_listener }
|
- { name: kernel.event_listener }
|
||||||
|
|
||||||
@@ -38,13 +38,13 @@ services:
|
|||||||
Symfony\Bridge\Monolog\Processor\TokenProcessor:
|
Symfony\Bridge\Monolog\Processor\TokenProcessor:
|
||||||
tags:
|
tags:
|
||||||
- { name: monolog.processor, channel: 'app' }
|
- { name: monolog.processor, channel: 'app' }
|
||||||
App\SharedKernel\Infrastructure\Framework\Symfony\Logging\TelegramFormatter:
|
Basango\SharedKernel\Infrastructure\Framework\Symfony\Logging\TelegramFormatter:
|
||||||
tags:
|
tags:
|
||||||
- { name: monolog.formatter }
|
- { name: monolog.formatter }
|
||||||
|
|
||||||
# makes classes in src/ available to be used as services
|
# makes classes in src/ available to be used as services
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
App\:
|
Basango\:
|
||||||
resource: '../src/'
|
resource: '../src/'
|
||||||
exclude:
|
exclude:
|
||||||
- '../src/SharedKernel/Infrastructure/Framework/Symfony/Kernel.php'
|
- '../src/SharedKernel/Infrastructure/Framework/Symfony/Kernel.php'
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\SharedKernel\Infrastructure\Framework\Symfony\Kernel;
|
use Basango\SharedKernel\Infrastructure\Framework\Symfony\Kernel;
|
||||||
|
|
||||||
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\EventListener;
|
namespace Basango\Aggregator\Application\EventListener;
|
||||||
|
|
||||||
use App\Aggregator\Application\Mailing\SourceCrawledEmail;
|
use Basango\Aggregator\Application\Mailing\SourceCrawledEmail;
|
||||||
use App\Aggregator\Domain\Event\SourceCrawled;
|
use Basango\Aggregator\Domain\Event\SourceCrawled;
|
||||||
use App\SharedKernel\Application\Mailing\Mailer;
|
use Basango\SharedKernel\Application\Mailing\Mailer;
|
||||||
use App\SharedKernel\Domain\EventListener\EventListener;
|
use Basango\SharedKernel\Domain\EventListener\EventListener;
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\EmailAddress;
|
use Basango\SharedKernel\Domain\Model\ValueObject\EmailAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceFetchedListener.
|
* Class SourceFetchedListener.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\Mailing;
|
namespace Basango\Aggregator\Application\Mailing;
|
||||||
|
|
||||||
use App\SharedKernel\Application\Mailing\EmailDefinition;
|
use Basango\SharedKernel\Application\Mailing\EmailDefinition;
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\EmailAddress;
|
use Basango\SharedKernel\Domain\Model\ValueObject\EmailAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceFetched.
|
* Class SourceFetched.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\ReadModel;
|
namespace Basango\Aggregator\Application\ReadModel;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\ArticleId;
|
use Basango\Aggregator\Domain\Model\Identity\ArticleId;
|
||||||
use App\SharedKernel\Domain\DataTransfert\DataMapping;
|
use Basango\SharedKernel\Domain\DataTransfert\DataMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ExportedArticle.
|
* Class ExportedArticle.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\ReadModel;
|
namespace Basango\Aggregator\Application\ReadModel;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\SourceId;
|
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||||
use App\SharedKernel\Domain\DataTransfert\DataMapping;
|
use Basango\SharedKernel\Domain\DataTransfert\DataMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceStatistics.
|
* Class SourceStatistics.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\ReadModel;
|
namespace Basango\Aggregator\Application\ReadModel;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Assert;
|
use Basango\SharedKernel\Domain\Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceStatisticsList.
|
* Class SourceStatisticsList.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Command;
|
namespace Basango\Aggregator\Application\UseCase\Command;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Link;
|
use Basango\Aggregator\Domain\Model\ValueObject\Link;
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphObject;
|
use Basango\Aggregator\Domain\Model\ValueObject\OpenGraph;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Save.
|
* Class Save.
|
||||||
@@ -21,7 +21,7 @@ final readonly class CreateArticle
|
|||||||
public string $body,
|
public string $body,
|
||||||
public string $source,
|
public string $source,
|
||||||
public int $timestamp,
|
public int $timestamp,
|
||||||
public ?OpenGraphObject $metadata = null
|
public ?OpenGraph $metadata = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Command;
|
namespace Basango\Aggregator\Application\UseCase\Command;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CreateSource.
|
* Class CreateSource.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Command;
|
namespace Basango\Aggregator\Application\UseCase\Command;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DeleteArticles.
|
* Class DeleteArticles.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Command;
|
namespace Basango\Aggregator\Application\UseCase\Command;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Export.
|
* Class Export.
|
||||||
|
|||||||
+8
-8
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\CommandHandler;
|
namespace Basango\Aggregator\Application\UseCase\CommandHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Command\CreateArticle;
|
use Basango\Aggregator\Application\UseCase\Command\CreateArticle;
|
||||||
use App\Aggregator\Domain\Exception\DuplicatedArticle;
|
use Basango\Aggregator\Domain\Exception\DuplicatedArticle;
|
||||||
use App\Aggregator\Domain\Model\Entity\Article;
|
use Basango\Aggregator\Domain\Model\Entity\Article;
|
||||||
use App\Aggregator\Domain\Model\Repository\ArticleRepository;
|
use Basango\Aggregator\Domain\Model\Repository\ArticleRepository;
|
||||||
use App\Aggregator\Domain\Model\Repository\SourceRepository;
|
use Basango\Aggregator\Domain\Model\Repository\SourceRepository;
|
||||||
use App\Aggregator\Domain\Service\HashCalculator;
|
use Basango\Aggregator\Domain\Service\HashCalculator;
|
||||||
use App\SharedKernel\Application\Messaging\CommandHandler;
|
use Basango\SharedKernel\Application\Messaging\CommandHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CreateArticlesHandler.
|
* Class CreateArticlesHandler.
|
||||||
|
|||||||
+5
-5
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\CommandHandler;
|
namespace Basango\Aggregator\Application\UseCase\CommandHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Command\CreateSource;
|
use Basango\Aggregator\Application\UseCase\Command\CreateSource;
|
||||||
use App\Aggregator\Domain\Model\Entity\Source;
|
use Basango\Aggregator\Domain\Model\Entity\Source;
|
||||||
use App\Aggregator\Domain\Model\Repository\SourceRepository;
|
use Basango\Aggregator\Domain\Model\Repository\SourceRepository;
|
||||||
use App\SharedKernel\Application\Messaging\CommandHandler;
|
use Basango\SharedKernel\Application\Messaging\CommandHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AddSourceHandler.
|
* Class AddSourceHandler.
|
||||||
|
|||||||
+4
-4
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\CommandHandler;
|
namespace Basango\Aggregator\Application\UseCase\CommandHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Command\DeleteArticles;
|
use Basango\Aggregator\Application\UseCase\Command\DeleteArticles;
|
||||||
use App\Aggregator\Domain\Model\Repository\ArticleRepository;
|
use Basango\Aggregator\Domain\Model\Repository\ArticleRepository;
|
||||||
use App\SharedKernel\Application\Messaging\CommandHandler;
|
use Basango\SharedKernel\Application\Messaging\CommandHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DeleteArticlesHandler.
|
* Class DeleteArticlesHandler.
|
||||||
|
|||||||
+8
-8
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\CommandHandler;
|
namespace Basango\Aggregator\Application\UseCase\CommandHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\ReadModel\ArticleForExport;
|
use Basango\Aggregator\Application\ReadModel\ArticleForExport;
|
||||||
use App\Aggregator\Application\UseCase\Command\ExportArticles;
|
use Basango\Aggregator\Application\UseCase\Command\ExportArticles;
|
||||||
use App\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
use Basango\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
||||||
use App\SharedKernel\Application\Messaging\CommandHandler;
|
use Basango\SharedKernel\Application\Messaging\CommandHandler;
|
||||||
use App\SharedKernel\Application\Messaging\QueryBus;
|
use Basango\SharedKernel\Application\Messaging\QueryBus;
|
||||||
use App\SharedKernel\Domain\DataTransfert\DataExporter;
|
use Basango\SharedKernel\Domain\DataTransfert\DataExporter;
|
||||||
use App\SharedKernel\Domain\DataTransfert\TransfertSetting;
|
use Basango\SharedKernel\Domain\DataTransfert\TransfertSetting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetArticlesForExportHandler.
|
* Class GetArticlesForExportHandler.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Query;
|
namespace Basango\Aggregator\Application\UseCase\Query;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetArticlesForExport.
|
* Class GetArticlesForExport.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Query;
|
namespace Basango\Aggregator\Application\UseCase\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetEarliestPublicationDate.
|
* Class GetEarliestPublicationDate.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Query;
|
namespace Basango\Aggregator\Application\UseCase\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetLatestPublicationDate.
|
* Class GetLatestPublicationDate.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\Query;
|
namespace Basango\Aggregator\Application\UseCase\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetSourceStatisticsList.
|
* Class GetSourceStatisticsList.
|
||||||
|
|||||||
+4
-4
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\QueryHandler;
|
namespace Basango\Aggregator\Application\UseCase\QueryHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\ReadModel\ArticleForExport;
|
use Basango\Aggregator\Application\ReadModel\ArticleForExport;
|
||||||
use App\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
use Basango\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
||||||
use App\SharedKernel\Application\Messaging\QueryHandler;
|
use Basango\SharedKernel\Application\Messaging\QueryHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GetArticlesForExportHandler.
|
* Class GetArticlesForExportHandler.
|
||||||
|
|||||||
+3
-3
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\QueryHandler;
|
namespace Basango\Aggregator\Application\UseCase\QueryHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Query\GetEarliestPublicationDate;
|
use Basango\Aggregator\Application\UseCase\Query\GetEarliestPublicationDate;
|
||||||
use App\SharedKernel\Application\Messaging\QueryHandler;
|
use Basango\SharedKernel\Application\Messaging\QueryHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface GetEarliestPublicationDateHandler.
|
* Interface GetEarliestPublicationDateHandler.
|
||||||
|
|||||||
+3
-3
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\QueryHandler;
|
namespace Basango\Aggregator\Application\UseCase\QueryHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Query\GetLatestPublicationDate;
|
use Basango\Aggregator\Application\UseCase\Query\GetLatestPublicationDate;
|
||||||
use App\SharedKernel\Application\Messaging\QueryHandler;
|
use Basango\SharedKernel\Application\Messaging\QueryHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface GetLatestPublicationDateHandler.
|
* Interface GetLatestPublicationDateHandler.
|
||||||
|
|||||||
+4
-4
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Application\UseCase\QueryHandler;
|
namespace Basango\Aggregator\Application\UseCase\QueryHandler;
|
||||||
|
|
||||||
use App\Aggregator\Application\ReadModel\SourceStatisticsList;
|
use Basango\Aggregator\Application\ReadModel\SourceStatisticsList;
|
||||||
use App\Aggregator\Application\UseCase\Query\GetSourceStatisticsList;
|
use Basango\Aggregator\Application\UseCase\Query\GetSourceStatisticsList;
|
||||||
use App\SharedKernel\Application\Messaging\QueryHandler;
|
use Basango\SharedKernel\Application\Messaging\QueryHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface GetSourceStatisticsListHandler.
|
* Interface GetSourceStatisticsListHandler.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Event;
|
namespace Basango\Aggregator\Domain\Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceFetched.
|
* Class SourceFetched.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Exception;
|
namespace Basango\Aggregator\Domain\Exception;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\ArticleId;
|
use Basango\Aggregator\Domain\Model\Identity\ArticleId;
|
||||||
use App\SharedKernel\Domain\Exception\UserFacingError;
|
use Basango\SharedKernel\Domain\Exception\UserFacingError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ArticleNotFound.
|
* Class ArticleNotFound.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Exception;
|
namespace Basango\Aggregator\Domain\Exception;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Exception\UserFacingError;
|
use Basango\SharedKernel\Domain\Exception\UserFacingError;
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ArticleOutOfRange.
|
* Class ArticleOutOfRange.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Exception;
|
namespace Basango\Aggregator\Domain\Exception;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Link;
|
use Basango\Aggregator\Domain\Model\ValueObject\Link;
|
||||||
use App\SharedKernel\Domain\Exception\UserFacingError;
|
use Basango\SharedKernel\Domain\Exception\UserFacingError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DuplicatedArticle.
|
* Class DuplicatedArticle.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Exception;
|
namespace Basango\Aggregator\Domain\Exception;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Exception\UserFacingError;
|
use Basango\SharedKernel\Domain\Exception\UserFacingError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DuplicatedArticle.
|
* Class DuplicatedArticle.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Exception;
|
namespace Basango\Aggregator\Domain\Exception;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\SourceId;
|
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||||
use App\SharedKernel\Domain\Exception\UserFacingError;
|
use Basango\SharedKernel\Domain\Exception\UserFacingError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SourceNotFound.
|
* Class SourceNotFound.
|
||||||
|
|||||||
@@ -2,15 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Entity;
|
namespace Basango\Aggregator\Domain\Model\Entity;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\ArticleId;
|
use Basango\Aggregator\Domain\Model\Identity\ArticleId;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\OpenGraph;
|
use Basango\Aggregator\Domain\Model\ValueObject\Link;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Link;
|
use Basango\Aggregator\Domain\Model\ValueObject\OpenGraph;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\ReadingTime;
|
use Basango\Aggregator\Domain\Model\ValueObject\ReadingTime;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment;
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphObject;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Article.
|
* Class Article.
|
||||||
@@ -73,22 +72,14 @@ class Article
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function defineOpenGraph(?OpenGraphObject $object): self
|
public function defineOpenGraph(?OpenGraph $object): self
|
||||||
{
|
{
|
||||||
if ($object instanceof OpenGraphObject) {
|
|
||||||
$image = $object->images[0] ?? null;
|
|
||||||
$video = $object->videos[0] ?? null;
|
|
||||||
$audio = $object->audios[0] ?? null;
|
|
||||||
|
|
||||||
$this->metadata = new OpenGraph(
|
$this->metadata = new OpenGraph(
|
||||||
title: $object->title,
|
title: $object->title,
|
||||||
description: $object->description,
|
description: $object->description,
|
||||||
image: $image->url ?? $image?->secureUrl,
|
image: $object->image,
|
||||||
video: $video->url ?? $video?->secureUrl,
|
locale: $object->locale ?? "fr"
|
||||||
audio: $audio->url ?? $audio?->secureUrl,
|
|
||||||
locale: $object->locale
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Entity;
|
namespace Basango\Aggregator\Domain\Model\Entity;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\CategoryId;
|
use Basango\Aggregator\Domain\Model\Identity\CategoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Category.
|
* Class Category.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Entity;
|
namespace Basango\Aggregator\Domain\Model\Entity;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Identity\SourceId;
|
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Source.
|
* Class Source.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Identity;
|
namespace Basango\Aggregator\Domain\Model\Identity;
|
||||||
|
|
||||||
use Symfony\Component\Uid\UuidV7;
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Identity;
|
namespace Basango\Aggregator\Domain\Model\Identity;
|
||||||
|
|
||||||
use Symfony\Component\Uid\UuidV7;
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Identity;
|
namespace Basango\Aggregator\Domain\Model\Identity;
|
||||||
|
|
||||||
use Symfony\Component\Uid\UuidV7;
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Repository;
|
namespace Basango\Aggregator\Domain\Model\Repository;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Entity\Article;
|
use Basango\Aggregator\Domain\Model\Entity\Article;
|
||||||
use App\Aggregator\Domain\Model\Identity\ArticleId;
|
use Basango\Aggregator\Domain\Model\Identity\ArticleId;
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface ArticleRepository.
|
* Interface ArticleRepository.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\Repository;
|
namespace Basango\Aggregator\Domain\Model\Repository;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\Entity\Source;
|
use Basango\Aggregator\Domain\Model\Entity\Source;
|
||||||
use App\Aggregator\Domain\Model\Identity\SourceId;
|
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface SourceRepository.
|
* Interface SourceRepository.
|
||||||
|
|||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Crawling;
|
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Assert;
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class FetchConfig.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class CrawlingSettings
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public string $id,
|
|
||||||
public ?PageRange $pageRange = null,
|
|
||||||
public ?DateRange $dateRange = null,
|
|
||||||
public ?string $category = null,
|
|
||||||
public bool $notify = false
|
|
||||||
) {
|
|
||||||
Assert::notEmpty($this->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Crawling;
|
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class PageRange.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class PageRange implements \Stringable
|
|
||||||
{
|
|
||||||
public int $start;
|
|
||||||
|
|
||||||
public int $end;
|
|
||||||
|
|
||||||
private function __construct(int $start, int $end)
|
|
||||||
{
|
|
||||||
Assert::greaterThanEq($start, 0);
|
|
||||||
Assert::greaterThanEq($end, 0);
|
|
||||||
Assert::greaterThan($end, $start);
|
|
||||||
|
|
||||||
$this->start = $start;
|
|
||||||
$this->end = $end;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function __toString(): string
|
|
||||||
{
|
|
||||||
return $this->start . ':' . $this->end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function from(string $interval): self
|
|
||||||
{
|
|
||||||
[$start, $end] = explode(':', $interval);
|
|
||||||
|
|
||||||
$start = (int) $start;
|
|
||||||
$end = (int) $end;
|
|
||||||
|
|
||||||
return new self($start, $end);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function inRange(int $page): bool
|
|
||||||
{
|
|
||||||
return $page >= $this->start && $page <= $this->end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Crawling;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class UpdateDirection.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
enum UpdateDirection: string
|
|
||||||
{
|
|
||||||
case FORWARD = 'forward';
|
|
||||||
case BACKWARD = 'backward';
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject;
|
namespace Basango\Aggregator\Domain\Model\ValueObject;
|
||||||
|
|
||||||
use App\SharedKernel\Domain\Assert;
|
use Basango\SharedKernel\Domain\Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Link.
|
* Class Link.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Crawling;
|
namespace Basango\Aggregator\Domain\Model\ValueObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OpenGraphMeta.
|
* Class OpenGraphMeta.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject;
|
namespace Basango\Aggregator\Domain\Model\ValueObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ReadingTime.
|
* Class ReadingTime.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Scoring;
|
namespace Basango\Aggregator\Domain\Model\ValueObject\Scoring;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Bias.
|
* Class Bias.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Scoring;
|
namespace Basango\Aggregator\Domain\Model\ValueObject\Scoring;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Credibility.
|
* Class Credibility.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Scoring;
|
namespace Basango\Aggregator\Domain\Model\ValueObject\Scoring;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Reliability.
|
* Class Reliability.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Scoring;
|
namespace Basango\Aggregator\Domain\Model\ValueObject\Scoring;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum Sentiment.
|
* Enum Sentiment.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Model\ValueObject\Scoring;
|
namespace Basango\Aggregator\Domain\Model\ValueObject\Scoring;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum Transparency.
|
* Enum Transparency.
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class DateParser.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class DateParser
|
|
||||||
{
|
|
||||||
public const array MONTHS = [
|
|
||||||
'janvier' => '01',
|
|
||||||
'février' => '02',
|
|
||||||
'mars' => '03',
|
|
||||||
'avril' => '04',
|
|
||||||
'mai' => '05',
|
|
||||||
'juin' => '06',
|
|
||||||
'juillet' => '07',
|
|
||||||
'août' => '08',
|
|
||||||
'septembre' => '09',
|
|
||||||
'octobre' => '10',
|
|
||||||
'novembre' => '11',
|
|
||||||
'décembre' => '12',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const array DAYS = [
|
|
||||||
'dimanche' => '0',
|
|
||||||
'lundi' => '1',
|
|
||||||
'mardi' => '2',
|
|
||||||
'mercredi' => '3',
|
|
||||||
'jeudi' => '4',
|
|
||||||
'vendredi' => '5',
|
|
||||||
'samedi' => '6',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const string DEFAULT_DATE_FORMAT = 'Y-m-d H:i';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function createTimeStamp(
|
|
||||||
string $date,
|
|
||||||
?string $format = null,
|
|
||||||
?string $pattern = null,
|
|
||||||
?string $replacement = null
|
|
||||||
): string {
|
|
||||||
/** @var string $date */
|
|
||||||
$date = strtr(strtr(strtolower($date), self::DAYS), self::MONTHS);
|
|
||||||
if ($pattern !== null && $replacement !== null) {
|
|
||||||
/** @var string $date */
|
|
||||||
$date = preg_replace(
|
|
||||||
pattern: $pattern,
|
|
||||||
replacement: $replacement,
|
|
||||||
subject: $date
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format === 'c') {
|
|
||||||
$date = str_replace('t', ' ', $date);
|
|
||||||
$format = 'Y-m-d H:i:s';
|
|
||||||
}
|
|
||||||
|
|
||||||
$datetime = \DateTime::createFromFormat($format ?? self::DEFAULT_DATE_FORMAT, $date);
|
|
||||||
|
|
||||||
return $datetime !== false ?
|
|
||||||
$datetime->format('U') :
|
|
||||||
new \DateTime('midnight')->format('U');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphElement;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Audio.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final class Audio extends OpenGraphElement
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public ?string $url = null,
|
|
||||||
public ?string $secureUrl = null,
|
|
||||||
public ?string $type = null
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportedProperties(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
OpenGraphProperty::AUDIO_URL => $this->url,
|
|
||||||
OpenGraphProperty::AUDIO_SECURE_URL => $this->secureUrl,
|
|
||||||
OpenGraphProperty::AUDIO_TYPE => $this->type,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphElement;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Image.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final class Image extends OpenGraphElement
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public ?string $url = null,
|
|
||||||
public ?string $secureUrl = null,
|
|
||||||
public ?string $type = null,
|
|
||||||
public ?int $width = null,
|
|
||||||
public ?int $height = null,
|
|
||||||
public ?bool $userGenerated = null
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportedProperties(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
OpenGraphProperty::IMAGE => $this->url,
|
|
||||||
OpenGraphProperty::IMAGE_SECURE_URL => $this->secureUrl,
|
|
||||||
OpenGraphProperty::IMAGE_TYPE => $this->type,
|
|
||||||
OpenGraphProperty::IMAGE_WIDTH => $this->width,
|
|
||||||
OpenGraphProperty::IMAGE_HEIGHT => $this->height,
|
|
||||||
OpenGraphProperty::IMAGE_USER_GENERATED => $this->userGenerated,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphElement;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Video.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final class Video extends OpenGraphElement
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public ?string $url = null,
|
|
||||||
public ?string $secureUrl = null,
|
|
||||||
public ?string $type = null,
|
|
||||||
public ?int $width = null,
|
|
||||||
public ?int $height = null
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportedProperties(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
OpenGraphProperty::VIDEO_URL => $this->url,
|
|
||||||
OpenGraphProperty::VIDEO_SECURE_URL => $this->secureUrl,
|
|
||||||
OpenGraphProperty::VIDEO_TYPE => $this->type,
|
|
||||||
OpenGraphProperty::VIDEO_WIDTH => $this->width,
|
|
||||||
OpenGraphProperty::VIDEO_HEIGHT => $this->height,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph\Objects;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Website.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final class Website extends OpenGraphObject
|
|
||||||
{
|
|
||||||
}
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface OpenGraphConsumer.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
interface OpenGraphConsumer
|
|
||||||
{
|
|
||||||
public function consumeUrl(string $url): ?OpenGraphObject;
|
|
||||||
|
|
||||||
public function consumeHtml(string $html, string $fallbackUrl): ?OpenGraphObject;
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class GraphElement.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
abstract class OpenGraphElement
|
|
||||||
{
|
|
||||||
abstract public function supportedProperties(): array;
|
|
||||||
|
|
||||||
public function getProperties(): array
|
|
||||||
{
|
|
||||||
return array_filter(
|
|
||||||
array_map(
|
|
||||||
fn (string $key, mixed $value): ?OpenGraphProperty => $value !== null ? new OpenGraphProperty($key, $value) : null,
|
|
||||||
array_keys($this->supportedProperties()),
|
|
||||||
array_values($this->supportedProperties())
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-283
@@ -1,283 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements\Audio;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements\Image;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\Elements\Video;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class GraphObject.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
abstract class OpenGraphObject
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public array $audios = [],
|
|
||||||
public ?string $description = null,
|
|
||||||
public ?string $determiner = null,
|
|
||||||
public array $images = [],
|
|
||||||
public ?string $locale = null,
|
|
||||||
public array $localeAlternate = [],
|
|
||||||
public ?bool $richAttachment = null,
|
|
||||||
public array $seeAlso = [],
|
|
||||||
public ?string $siteName = null,
|
|
||||||
public ?string $title = null,
|
|
||||||
public ?string $type = null,
|
|
||||||
public ?\DateTimeImmutable $updatedTime = null,
|
|
||||||
public ?string $url = null,
|
|
||||||
public array $videos = []
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function assignProperties(array $properties, bool $debug = false): void
|
|
||||||
{
|
|
||||||
foreach ($properties as $property) {
|
|
||||||
$name = $property->key;
|
|
||||||
$value = $property->value;
|
|
||||||
|
|
||||||
switch ($name) {
|
|
||||||
case OpenGraphProperty::AUDIO:
|
|
||||||
case OpenGraphProperty::AUDIO_URL:
|
|
||||||
$this->audios[] = new Audio($value);
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::AUDIO_SECURE_URL:
|
|
||||||
case OpenGraphProperty::AUDIO_TYPE:
|
|
||||||
if ($this->audios !== []) {
|
|
||||||
$this->handleAudioAttribute($this->audios[\count($this->audios) - 1], $name, $value);
|
|
||||||
} elseif ($debug) {
|
|
||||||
throw new \UnexpectedValueException(
|
|
||||||
\sprintf(
|
|
||||||
"Found '%s' property but no audio was found before.",
|
|
||||||
$name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::DESCRIPTION:
|
|
||||||
if ($this->description === null) {
|
|
||||||
$this->description = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::DETERMINER:
|
|
||||||
if ($this->determiner === null) {
|
|
||||||
$this->determiner = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE:
|
|
||||||
case OpenGraphProperty::IMAGE_URL:
|
|
||||||
$this->images[] = new Image($value);
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE_HEIGHT:
|
|
||||||
case OpenGraphProperty::IMAGE_SECURE_URL:
|
|
||||||
case OpenGraphProperty::IMAGE_TYPE:
|
|
||||||
case OpenGraphProperty::IMAGE_WIDTH:
|
|
||||||
case OpenGraphProperty::IMAGE_USER_GENERATED:
|
|
||||||
if ($this->images !== []) {
|
|
||||||
$this->handleImageAttribute($this->images[\count($this->images) - 1], $name, $value);
|
|
||||||
} elseif ($debug) {
|
|
||||||
throw new \UnexpectedValueException(
|
|
||||||
\sprintf(
|
|
||||||
"Found '%s' property but no image was found before.",
|
|
||||||
$name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::LOCALE:
|
|
||||||
if ($this->locale === null) {
|
|
||||||
$this->locale = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::LOCALE_ALTERNATE:
|
|
||||||
$this->localeAlternate[] = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::RICH_ATTACHMENT:
|
|
||||||
$this->richAttachment = $this->convertToBoolean($value);
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::SEE_ALSO:
|
|
||||||
$this->seeAlso[] = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::SITE_NAME:
|
|
||||||
if ($this->siteName === null) {
|
|
||||||
$this->siteName = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::TITLE:
|
|
||||||
if ($this->title === null) {
|
|
||||||
$this->title = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::UPDATED_TIME:
|
|
||||||
if (! $this->updatedTime instanceof \DateTimeImmutable) {
|
|
||||||
$this->updatedTime = $this->convertToDateTime($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::URL:
|
|
||||||
if ($this->url === null) {
|
|
||||||
$this->url = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::VIDEO:
|
|
||||||
case OpenGraphProperty::VIDEO_URL:
|
|
||||||
$this->videos[] = new Video($value);
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::VIDEO_HEIGHT:
|
|
||||||
case OpenGraphProperty::VIDEO_SECURE_URL:
|
|
||||||
case OpenGraphProperty::VIDEO_TYPE:
|
|
||||||
case OpenGraphProperty::VIDEO_WIDTH:
|
|
||||||
if ($this->videos !== []) {
|
|
||||||
$this->handleVideoAttribute($this->videos[\count($this->videos) - 1], $name, $value);
|
|
||||||
} elseif ($debug) {
|
|
||||||
throw new \UnexpectedValueException(\sprintf(
|
|
||||||
"Found '%s' property but no video was found before.",
|
|
||||||
$name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getProperties(): array
|
|
||||||
{
|
|
||||||
$properties = [];
|
|
||||||
|
|
||||||
foreach ($this->audios as $audio) {
|
|
||||||
$properties = array_merge($properties, $audio->getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->title !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::TITLE, $this->title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->description !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::DESCRIPTION, $this->description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->determiner !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::DETERMINER, $this->determiner);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->images as $image) {
|
|
||||||
$properties = array_merge($properties, $image->getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->locale !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::LOCALE, $this->locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->localeAlternate as $locale) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::LOCALE_ALTERNATE, $locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->richAttachment !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::RICH_ATTACHMENT, (int) $this->richAttachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->seeAlso as $seeAlso) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::SEE_ALSO, $seeAlso);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->siteName !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::SITE_NAME, $this->siteName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->type !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::TYPE, $this->type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->updatedTime instanceof \DateTimeImmutable) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::UPDATED_TIME, $this->updatedTime->format('c'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->url !== null) {
|
|
||||||
$properties[] = new OpenGraphProperty(OpenGraphProperty::URL, $this->url);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->videos as $video) {
|
|
||||||
$properties = array_merge($properties, $video->getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function convertToBoolean(string $value): bool
|
|
||||||
{
|
|
||||||
return match (strtolower($value)) {
|
|
||||||
'1', 'true' => true,
|
|
||||||
default => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function convertToDateTime(string $value): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return new \DateTimeImmutable($value);
|
|
||||||
} catch (\Throwable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleAudioAttribute(Audio $element, string $name, string $value): void
|
|
||||||
{
|
|
||||||
switch ($name) {
|
|
||||||
case OpenGraphProperty::AUDIO_TYPE:
|
|
||||||
$element->type = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::AUDIO_SECURE_URL:
|
|
||||||
$element->secureUrl = $value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleImageAttribute(Image $element, string $name, string $value): void
|
|
||||||
{
|
|
||||||
switch ($name) {
|
|
||||||
case OpenGraphProperty::IMAGE_HEIGHT:
|
|
||||||
$element->height = (int) $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE_WIDTH:
|
|
||||||
$element->width = (int) $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE_TYPE:
|
|
||||||
$element->type = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE_SECURE_URL:
|
|
||||||
$element->secureUrl = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::IMAGE_USER_GENERATED:
|
|
||||||
$element->userGenerated = $this->convertToBoolean($value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleVideoAttribute(Video $element, string $name, string $value): void
|
|
||||||
{
|
|
||||||
switch ($name) {
|
|
||||||
case OpenGraphProperty::VIDEO_HEIGHT:
|
|
||||||
$element->height = (int) $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::VIDEO_WIDTH:
|
|
||||||
$element->width = (int) $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::VIDEO_TYPE:
|
|
||||||
$element->type = $value;
|
|
||||||
break;
|
|
||||||
case OpenGraphProperty::VIDEO_SECURE_URL:
|
|
||||||
$element->secureUrl = $value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling\OpenGraph;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Property.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class OpenGraphProperty
|
|
||||||
{
|
|
||||||
public const string AUDIO = 'og:audio';
|
|
||||||
|
|
||||||
public const string AUDIO_SECURE_URL = 'og:audio:secure_url';
|
|
||||||
|
|
||||||
public const string AUDIO_TYPE = 'og:audio:type';
|
|
||||||
|
|
||||||
public const string AUDIO_URL = 'og:audio:url';
|
|
||||||
|
|
||||||
public const string DESCRIPTION = 'og:description';
|
|
||||||
|
|
||||||
public const string DETERMINER = 'og:determiner';
|
|
||||||
|
|
||||||
public const string IMAGE = 'og:image';
|
|
||||||
|
|
||||||
public const string IMAGE_HEIGHT = 'og:image:height';
|
|
||||||
|
|
||||||
public const string IMAGE_SECURE_URL = 'og:image:secure_url';
|
|
||||||
|
|
||||||
public const string IMAGE_TYPE = 'og:image:type';
|
|
||||||
|
|
||||||
public const string IMAGE_URL = 'og:image:url';
|
|
||||||
|
|
||||||
public const string IMAGE_WIDTH = 'og:image:width';
|
|
||||||
|
|
||||||
public const string IMAGE_USER_GENERATED = 'og:image:user_generated';
|
|
||||||
|
|
||||||
public const string LOCALE = 'og:locale';
|
|
||||||
|
|
||||||
public const string LOCALE_ALTERNATE = 'og:locale:alternate';
|
|
||||||
|
|
||||||
public const string RICH_ATTACHMENT = 'og:rich_attachment';
|
|
||||||
|
|
||||||
public const string SEE_ALSO = 'og:see_also';
|
|
||||||
|
|
||||||
public const string SITE_NAME = 'og:site_name';
|
|
||||||
|
|
||||||
public const string TITLE = 'og:title';
|
|
||||||
|
|
||||||
public const string TYPE = 'og:type';
|
|
||||||
|
|
||||||
public const string UPDATED_TIME = 'og:updated_time';
|
|
||||||
|
|
||||||
public const string URL = 'og:url';
|
|
||||||
|
|
||||||
public const string VIDEO = 'og:video';
|
|
||||||
|
|
||||||
public const string VIDEO_HEIGHT = 'og:video:height';
|
|
||||||
|
|
||||||
public const string VIDEO_SECURE_URL = 'og:video:secure_url';
|
|
||||||
|
|
||||||
public const string VIDEO_TYPE = 'og:video:type';
|
|
||||||
|
|
||||||
public const string VIDEO_URL = 'og:video:url';
|
|
||||||
|
|
||||||
public const string VIDEO_WIDTH = 'og:video:width';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public string $key,
|
|
||||||
public mixed $value,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Crawling;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\CrawlingSettings;
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface SourceCrawler.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
interface SourceCrawler
|
|
||||||
{
|
|
||||||
public function fetch(CrawlingSettings $settings): void;
|
|
||||||
|
|
||||||
public function fetchOne(string $html, ?DateRange $dateRange = null): void;
|
|
||||||
|
|
||||||
public function supports(string $source): bool;
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service;
|
namespace Basango\Aggregator\Domain\Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class HashCalculator.
|
* Class HashCalculator.
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Scoring;
|
namespace Basango\Aggregator\Domain\Service\Scoring;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Bias;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Bias;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Reliability;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Reliability;
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Transparency;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Transparency;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface CredibilityAnalyser.
|
* Interface CredibilityAnalyser.
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Domain\Service\Scoring;
|
namespace Basango\Aggregator\Domain\Service\Scoring;
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment;
|
use Basango\Aggregator\Domain\Model\ValueObject\Scoring\Sentiment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface SentimentAnalyser.
|
* Interface SentimentAnalyser.
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler;
|
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class HttpClientFactory.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class HttpClientFactory
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private string $projectDir,
|
|
||||||
private Filesystem $filesystem,
|
|
||||||
private HttpClientInterface $client,
|
|
||||||
private LoggerInterface $logger
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(): HttpClientInterface
|
|
||||||
{
|
|
||||||
$proxy = $this->getProxy();
|
|
||||||
|
|
||||||
return $this->client->withOptions([
|
|
||||||
'headers' => [
|
|
||||||
'User-Agent' => UserAgents::random(),
|
|
||||||
],
|
|
||||||
'proxy' => $proxy !== null ? 'https://' . $proxy : null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getProxy(): ?string
|
|
||||||
{
|
|
||||||
$flag = boolval(getenv('USE_PROXY'));
|
|
||||||
if ($flag === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$filename = sprintf('%s/data/proxies.txt', $this->projectDir);
|
|
||||||
$content = $this->filesystem->readFile($filename);
|
|
||||||
|
|
||||||
/** @var list<string> $proxies */
|
|
||||||
$proxies = preg_split('/\r\n|\n|\r/', $content);
|
|
||||||
$proxies = array_filter($proxies, static fn ($proxy): bool => $proxy !== '' && $proxy !== '0');
|
|
||||||
|
|
||||||
$proxy = $proxies[array_rand($proxies)];
|
|
||||||
$this->logger->info('HttpClient is using proxy: ' . $proxy);
|
|
||||||
|
|
||||||
return $proxy;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error('Unable to read proxy file', [
|
|
||||||
'exception' => $e,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-128
@@ -1,128 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler\OpenGraph;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\Objects\Website;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphConsumer;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphObject;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphProperty;
|
|
||||||
use App\Aggregator\Infrastructure\Crawler\HttpClientFactory;
|
|
||||||
use App\Aggregator\Infrastructure\Crawler\UserAgents;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class OpenGraphConsumer.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class DomCrawlerConsumer implements OpenGraphConsumer
|
|
||||||
{
|
|
||||||
private HttpClientInterface $client;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
HttpClientFactory $clientFactory,
|
|
||||||
private LoggerInterface $logger,
|
|
||||||
private bool $useFallbackMode = true,
|
|
||||||
private bool $debug = false,
|
|
||||||
) {
|
|
||||||
$this->client = $clientFactory->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function consumeUrl(string $url): ?OpenGraphObject
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$response = $this->client->request('GET', $url, [
|
|
||||||
'headers' => [
|
|
||||||
'User-Agent' => UserAgents::OPEN_GRAPH->value,
|
|
||||||
],
|
|
||||||
])->getContent();
|
|
||||||
|
|
||||||
return $this->consumeHtml($response, $url);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error(
|
|
||||||
'Unable to consume OpenGraph URL',
|
|
||||||
[
|
|
||||||
'url' => $url,
|
|
||||||
'exception' => $e,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function consumeHtml(string $html, string $fallbackUrl): ?OpenGraphObject
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$object = $this->consume($html);
|
|
||||||
|
|
||||||
if ($this->useFallbackMode && $object->url === null) {
|
|
||||||
$object->url = $fallbackUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $object;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error(
|
|
||||||
'Unable to consume OpenGraph HTML',
|
|
||||||
[
|
|
||||||
'html' => $html,
|
|
||||||
'exception' => $e,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function consume(string $content): OpenGraphObject
|
|
||||||
{
|
|
||||||
$crawler = new Crawler($content);
|
|
||||||
$object = new Website(type: 'website');
|
|
||||||
$properties = [];
|
|
||||||
|
|
||||||
foreach (['name', 'property'] as $t) {
|
|
||||||
$props = [];
|
|
||||||
|
|
||||||
/** @var \DOMElement $tag */
|
|
||||||
foreach ($crawler->filter(sprintf("meta[%s^='og:']", $t)) as $tag) {
|
|
||||||
$name = strtolower(trim($tag->getAttribute($t)));
|
|
||||||
$value = trim($tag->getAttribute('content'));
|
|
||||||
$props[] = new OpenGraphProperty($name, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$properties = array_merge($properties, $props);
|
|
||||||
}
|
|
||||||
|
|
||||||
$object->assignProperties($properties, $this->debug);
|
|
||||||
|
|
||||||
// Fallback for url
|
|
||||||
if ($this->useFallbackMode && $object->url === null) {
|
|
||||||
$urlElement = $crawler->filter("link[rel='canonical']")->first();
|
|
||||||
if ($urlElement->count() > 0) {
|
|
||||||
$object->url = trim($urlElement->attr('href') ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for title
|
|
||||||
if ($this->useFallbackMode && $object->title === null) {
|
|
||||||
$titleElement = $crawler->filter('title')->first();
|
|
||||||
if ($titleElement->count() > 0) {
|
|
||||||
$object->title = trim($titleElement->text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for description
|
|
||||||
if ($this->useFallbackMode && $object->description === null) {
|
|
||||||
$descriptionElement = $crawler->filter("meta[property='description']")->first();
|
|
||||||
if ($descriptionElement->count() > 0) {
|
|
||||||
$object->description = trim($descriptionElement->attr('content') ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler\Source;
|
|
||||||
|
|
||||||
use App\Aggregator\Application\UseCase\Command\CreateArticle;
|
|
||||||
use App\Aggregator\Domain\Event\SourceCrawled;
|
|
||||||
use App\Aggregator\Domain\Exception\ArticleOutOfRange;
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\PageRange;
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Link;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\DateParser;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphConsumer;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\OpenGraph\OpenGraphObject;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\SourceCrawler;
|
|
||||||
use App\Aggregator\Infrastructure\Crawler\HttpClientFactory;
|
|
||||||
use App\SharedKernel\Application\Messaging\CommandBus;
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
|
||||||
use Symfony\Component\Stopwatch\Stopwatch;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SourceFetcher.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
#[AutoconfigureTag('app.data_source')]
|
|
||||||
abstract class Source implements SourceCrawler
|
|
||||||
{
|
|
||||||
protected const string URL = 'url';
|
|
||||||
|
|
||||||
protected const string ID = 'id';
|
|
||||||
|
|
||||||
private const string WATCH_EVENT_NAME = 'crawling';
|
|
||||||
|
|
||||||
protected Stopwatch $stopwatch;
|
|
||||||
|
|
||||||
protected HttpClientInterface $client;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
HttpClientFactory $clientFactory,
|
|
||||||
protected EventDispatcherInterface $dispatcher,
|
|
||||||
protected LoggerInterface $logger,
|
|
||||||
protected DateParser $dateParser,
|
|
||||||
protected CommandBus $commandBus,
|
|
||||||
protected OpenGraphConsumer $openGraphConsumer
|
|
||||||
) {
|
|
||||||
$this->stopwatch = new Stopwatch();
|
|
||||||
$this->client = $clientFactory->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function supports(string $source): bool
|
|
||||||
{
|
|
||||||
return $source === $this->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function getPagination(?string $category = null): PageRange;
|
|
||||||
|
|
||||||
protected function getId(): string
|
|
||||||
{
|
|
||||||
return static::ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getUrl(): string
|
|
||||||
{
|
|
||||||
return static::URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
protected function crawle(string $url, ?int $page = null): Crawler
|
|
||||||
{
|
|
||||||
if ($page !== null) {
|
|
||||||
$this->logger->notice('> Page ' . $page);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $this->client->request('GET', $url)->getContent();
|
|
||||||
return new Crawler($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function save(
|
|
||||||
string $title,
|
|
||||||
string $link,
|
|
||||||
string $categories,
|
|
||||||
string $body,
|
|
||||||
string $timestamp,
|
|
||||||
?OpenGraphObject $metadata = null
|
|
||||||
): void {
|
|
||||||
try {
|
|
||||||
$this->commandBus->handle(
|
|
||||||
new CreateArticle(
|
|
||||||
title: $title,
|
|
||||||
link: Link::from($link, $this->getId()),
|
|
||||||
categories: $categories,
|
|
||||||
body: $body,
|
|
||||||
source: $this->getId(),
|
|
||||||
timestamp: (int) $timestamp,
|
|
||||||
metadata: $metadata
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->logger->notice(sprintf('> %s ✅', $title));
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error(sprintf('> %s [Failed] ❌', $e->getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function initialize(): void
|
|
||||||
{
|
|
||||||
$this->stopwatch->start(self::WATCH_EVENT_NAME);
|
|
||||||
$this->logger->notice('Initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function completed(bool $notify = false): void
|
|
||||||
{
|
|
||||||
$event = $this->stopwatch->stop(self::WATCH_EVENT_NAME);
|
|
||||||
$this->dispatcher->dispatch(new SourceCrawled((string) $event, $this->getId(), $notify));
|
|
||||||
$this->logger->notice('Done');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function skip(DateRange $dateRange, string $timestamp, string $title, string $date): void
|
|
||||||
{
|
|
||||||
if ($dateRange->outRange((int) $timestamp)) {
|
|
||||||
throw ArticleOutOfRange::with($timestamp, $dateRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->notice(sprintf('> %s [Skipped %s]', $title, $date));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
protected function getLastPage(?string $url = null): int
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
/** @var string $node */
|
|
||||||
$node = $this->crawle($url ?? $this->getUrl())
|
|
||||||
->filter('ul.pagination > li a')
|
|
||||||
->last()
|
|
||||||
->attr('href');
|
|
||||||
|
|
||||||
/** @var string $query */
|
|
||||||
$query = parse_url($node, PHP_URL_QUERY);
|
|
||||||
parse_str($query, $result);
|
|
||||||
|
|
||||||
return (int) $result['page'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler\Source;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\CrawlingSettings;
|
|
||||||
use App\Aggregator\Domain\Service\Crawling\SourceCrawler as SourceCrawlerInterface;
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
|
||||||
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SourceFetcher.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
final readonly class SourceCrawler implements SourceCrawlerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var iterable<SourceCrawlerInterface>
|
|
||||||
*/
|
|
||||||
private iterable $sources;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
#[AutowireIterator('app.data_source')] \Traversable $sources
|
|
||||||
) {
|
|
||||||
$this->sources = iterator_to_array($sources);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function fetch(CrawlingSettings $settings): void
|
|
||||||
{
|
|
||||||
foreach ($this->sources as $source) {
|
|
||||||
if ($source->supports($settings->id)) {
|
|
||||||
$source->fetch($settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function supports(string $source): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function fetchOne(string $html, ?DateRange $dateRange = null): void
|
|
||||||
{
|
|
||||||
throw new \RuntimeException('Not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $id): Source
|
|
||||||
{
|
|
||||||
/** @var Source $source */
|
|
||||||
foreach ($this->sources as $source) {
|
|
||||||
if ($source->supports($id)) {
|
|
||||||
return $source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new \RuntimeException('Source not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler\Source;
|
|
||||||
|
|
||||||
use App\Aggregator\Domain\Exception\ArticleOutOfRange;
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\CrawlingSettings;
|
|
||||||
use App\Aggregator\Domain\Model\ValueObject\Crawling\PageRange;
|
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class WordPressJson.
|
|
||||||
*
|
|
||||||
* Some WordPress websites expose their data in JSON format,
|
|
||||||
* this class will help to fetch data from those websites.
|
|
||||||
*
|
|
||||||
* @see https://developer.wordpress.org/rest-api/
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
class WordPressJson extends Source
|
|
||||||
{
|
|
||||||
public const string POST_QUERY = '_fields=date,slug,link,title.rendered,content.rendered,categories&orderby=date&order=desc';
|
|
||||||
|
|
||||||
public const string CATEGORY_QUERY = '_fields=id,slug,count&orderby=count&order=desc&per_page=100';
|
|
||||||
|
|
||||||
public const string TOTAL_PAGES_HEADER = 'x-wp-totalpages';
|
|
||||||
|
|
||||||
public const string TOTAL_POSTS_HEADER = 'x-wp-total';
|
|
||||||
|
|
||||||
private array $categoryMap = [];
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function getPagination(?string $category = null): PageRange
|
|
||||||
{
|
|
||||||
$response = $this->client->request('GET', sprintf('%s/wp-json/wp/v2/posts?_fields=id&per_page=100', $this->getUrl()));
|
|
||||||
$headers = $response->getHeaders();
|
|
||||||
$pages = (int) $headers[self::TOTAL_PAGES_HEADER][0];
|
|
||||||
$posts = (int) $headers[self::TOTAL_POSTS_HEADER][0];
|
|
||||||
|
|
||||||
$this->logger->notice(sprintf('WordPressJson %d posts, %d pages', $posts, $pages));
|
|
||||||
return PageRange::from(sprintf('1:%d', $pages));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function fetch(CrawlingSettings $settings): void
|
|
||||||
{
|
|
||||||
$this->initialize();
|
|
||||||
$page = $settings->pageRange ?? $this->getPagination();
|
|
||||||
|
|
||||||
for ($i = $page->start; $i <= $page->end; $i++) {
|
|
||||||
try {
|
|
||||||
$response = $this->client->request(
|
|
||||||
method: 'GET',
|
|
||||||
url: sprintf('%s/wp-json/wp/v2/posts?%s&page=%d&per_page=100', $this->getUrl(), self::POST_QUERY, $i)
|
|
||||||
);
|
|
||||||
|
|
||||||
/** @var array $articles */
|
|
||||||
$articles = json_decode($this->removeMisconfigurationError($response->getContent()), true);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error(sprintf('> page %d => %s [Failed] ❌', $i, $e->getMessage()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach ($articles as $article) {
|
|
||||||
$this->fetchOne((string) json_encode($article), $settings->dateRange);
|
|
||||||
}
|
|
||||||
} catch (ArticleOutOfRange) {
|
|
||||||
$this->logger->notice('No more articles to fetch in this range.');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->completed($settings->notify);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function fetchOne(string $html, ?DateRange $dateRange = null): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* @var array{
|
|
||||||
* link:string,
|
|
||||||
* title:array{rendered:string},
|
|
||||||
* content:array{rendered:string},
|
|
||||||
* date:string,
|
|
||||||
* categories:int[]
|
|
||||||
* } $data
|
|
||||||
*/
|
|
||||||
$data = json_decode($html, true);
|
|
||||||
|
|
||||||
$link = str_replace($this->getUrl(), '', $data['link']);
|
|
||||||
$title = strip_tags($data['title']['rendered']);
|
|
||||||
$body = strip_tags($data['content']['rendered']);
|
|
||||||
$timestamp = $this->dateParser->createTimeStamp($data['date'], format: 'c');
|
|
||||||
$categories = $this->mapCategories($data['categories']);
|
|
||||||
|
|
||||||
if (! $dateRange instanceof DateRange || $dateRange->inRange((int) $timestamp)) {
|
|
||||||
$metadata = $this->openGraphConsumer->consumeUrl($data['link']);
|
|
||||||
|
|
||||||
$this->save($title, $link, $categories, $body, $timestamp, $metadata);
|
|
||||||
} else {
|
|
||||||
$this->skip($dateRange, $timestamp, $title, $data['date']);
|
|
||||||
}
|
|
||||||
} catch (ArticleOutOfRange $e) {
|
|
||||||
throw $e;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error(sprintf('> %s [Failed] ❌', $e->getMessage()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* edge case for some politico.cd website
|
|
||||||
* this invalidates the json, so we have to remove it
|
|
||||||
*/
|
|
||||||
private function removeMisconfigurationError(string $content): string
|
|
||||||
{
|
|
||||||
$error = '<br />
|
|
||||||
<b>Notice</b>: ob_end_flush(): Failed to send buffer of zlib output compression (0) in <b>/home/politico/public_html/wp-includes/functions.php</b> on line <b>5427</b><br />';
|
|
||||||
return str_replace($error, '', $content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchCategories(): void
|
|
||||||
{
|
|
||||||
$response = $this->client->request('GET', sprintf('%s/wp-json/wp/v2/categories?%s', $this->getUrl(), self::CATEGORY_QUERY));
|
|
||||||
|
|
||||||
/** @var array{id: int, slug: string}[] $categories */
|
|
||||||
$categories = json_decode($response->getContent(), true);
|
|
||||||
|
|
||||||
foreach ($categories as $category) {
|
|
||||||
$this->categoryMap[$category['id']] = $category['slug'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function mapCategories(array $categories): string
|
|
||||||
{
|
|
||||||
if ($this->categoryMap === []) {
|
|
||||||
$this->fetchCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
return strtolower(implode(',', array_map(fn ($category) => $this->categoryMap[$category], $categories)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Crawler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class UserAgents.
|
|
||||||
*
|
|
||||||
* @author bernard-ng <bernard@devscast.tech>
|
|
||||||
*/
|
|
||||||
enum UserAgents: string
|
|
||||||
{
|
|
||||||
case OPEN_GRAPH = 'facebookexternalhit/1.1';
|
|
||||||
case IPHONE = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_4_8; like Mac OS X) AppleWebKit/603.39 (KHTML, like Gecko) Chrome/52.0.3638.271 Mobile Safari/537.5';
|
|
||||||
case LINUX = 'Mozilla/5.0 (Linux; U; Linux x86_64; en-US) Gecko/20130401 Firefox/52.7';
|
|
||||||
case ANDROID = 'Mozilla/5.0 (Linux; U; Android 5.0; SM-P815 Build/LRX22G) AppleWebKit/600.4 (KHTML, like Gecko) Chrome/48.0.1562.260 Mobile Safari/600.0';
|
|
||||||
case CHROME_WINDOWS = 'Mozilla/5.0 (Windows; U; Windows NT 6.3;) AppleWebKit/533.34 (KHTML, like Gecko) Chrome/51.0.1883.215 Safari/533';
|
|
||||||
case EXPLORER = 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.3; x64; en-US Trident/4.0)';
|
|
||||||
case MAC_FIREFOX = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_10_3) Gecko/20100101 Firefox/63.4';
|
|
||||||
case CHROME_LINUX = 'Mozilla/5.0 (Linux; Linux x86_64; en-US) AppleWebKit/603.50 (KHTML, like Gecko) Chrome/55.0.2226.116 Safari/601';
|
|
||||||
case MAC_FIREFOX_OLD = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 7_8_3; en-US) Gecko/20100101 Firefox/68.9';
|
|
||||||
case MOBILE_IPHONE = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_9_8; like Mac OS X) AppleWebKit/603.34 (KHTML, like Gecko) Chrome/47.0.1126.107 Mobile Safari/602.7';
|
|
||||||
case MOBILE_IPOD = 'Mozilla/5.0 (iPod; CPU iPod OS 8_2_0; like Mac OS X) AppleWebKit/601.40 (KHTML, like Gecko) Chrome/47.0.1590.178 Mobile Safari/535.2';
|
|
||||||
|
|
||||||
public static function random(): string
|
|
||||||
{
|
|
||||||
$userAgents = array_map(fn (self $userAgent) => $userAgent->value, self::cases());
|
|
||||||
return $userAgents[array_rand($userAgents)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-5
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Aggregator\Infrastructure\Persistence\Doctrine\DBAL;
|
namespace Basango\Aggregator\Infrastructure\Persistence\Doctrine\DBAL;
|
||||||
|
|
||||||
use App\Aggregator\Application\ReadModel\ArticleForExport;
|
use Basango\Aggregator\Application\ReadModel\ArticleForExport;
|
||||||
use App\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
use Basango\Aggregator\Application\UseCase\Query\GetArticlesForExport;
|
||||||
use App\Aggregator\Application\UseCase\QueryHandler\GetArticlesForExportHandler;
|
use Basango\Aggregator\Application\UseCase\QueryHandler\GetArticlesForExportHandler;
|
||||||
use App\SharedKernel\Domain\Model\ValueObject\DateRange;
|
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user