Compare commits
28 commits
master
...
feature_po
Author | SHA1 | Date | |
---|---|---|---|
49d4097a47 | |||
3f31705536 | |||
6b5a60b2a5 | |||
b0a6fbfb7f | |||
a12bf9d9a2 | |||
dbc1c060f8 | |||
b1d047941c | |||
4d7549bddd | |||
ac0d905ce4 | |||
16489b509e | |||
728464005e | |||
4ce5fe0ccb | |||
300dbcb466 | |||
d528b45436 | |||
f2b423ad8c | |||
4707b41f27 | |||
05aaa1d4e1 | |||
26ee4522fc | |||
63b27dc312 | |||
f5a4e5b896 | |||
4eb7b418db | |||
5897555302 | |||
7902f9b3ea | |||
f8dfe4e103 | |||
46b327b455 | |||
31d49eb270 | |||
5d2ce0fe42 | |||
e82ff0d2a1 |
|
@ -19,6 +19,7 @@ class AppKernel extends Kernel
|
|||
new Symfony\Bundle\WebServerBundle\WebServerBundle(),
|
||||
new JMS\SerializerBundle\JMSSerializerBundle(),
|
||||
new Csa\Bundle\GuzzleBundle\CsaGuzzleBundle(),
|
||||
new Leezy\PheanstalkBundle\LeezyPheanstalkBundle(),
|
||||
new Ob\HighchartsBundle\ObHighchartsBundle(),
|
||||
new Knp\Bundle\MarkdownBundle\KnpMarkdownBundle(),
|
||||
new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(),
|
||||
|
|
51
app/DoctrineMigrations/Version20180427143940.php
Normal file
51
app/DoctrineMigrations/Version20180427143940.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* Issue #44 - Post and Comment schema refactoring.
|
||||
* - Post subscription status added.
|
||||
* - Comments parent-child relations removed.
|
||||
* - User login index
|
||||
* - Other adjustments
|
||||
*/
|
||||
class Version20180427143940 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema)
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('ALTER TABLE posts.posts ADD is_subscribed BOOLEAN DEFAULT FALSE NOT NULL');
|
||||
// Removing parent_id constraint and index
|
||||
$this->addSql('ALTER TABLE posts.comments DROP CONSTRAINT fk_62899975727aca70');
|
||||
$this->addSql('DROP INDEX posts.idx_62899975727aca70');
|
||||
$this->addSql('ALTER TABLE posts.comments DROP parent_id');
|
||||
|
||||
$this->addSql('ALTER TABLE posts.comments ADD to_number INT');
|
||||
$this->addSql('ALTER TABLE posts.comments ALTER number TYPE INT');
|
||||
$this->addSql('ALTER TABLE posts.comments ALTER number DROP DEFAULT');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6289997596901F54 ON posts.comments (number)');
|
||||
|
||||
$this->addSql('CREATE INDEX idx_user_login ON users.users (login)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema)
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('ALTER TABLE posts.posts DROP is_subscribed');
|
||||
$this->addSql('DROP INDEX posts.UNIQ_6289997596901F54');
|
||||
$this->addSql('ALTER TABLE posts.comments ADD parent_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE posts.comments DROP to_number');
|
||||
$this->addSql('ALTER TABLE posts.comments ALTER number TYPE SMALLINT');
|
||||
$this->addSql('ALTER TABLE posts.comments ALTER number DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE posts.comments ADD CONSTRAINT fk_62899975727aca70 FOREIGN KEY (parent_id) REFERENCES posts.comments (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX idx_62899975727aca70 ON posts.comments (parent_id)');
|
||||
$this->addSql('DROP INDEX users.idx_user_login');
|
||||
}
|
||||
}
|
28
app/DoctrineMigrations/Version20190227233244.php
Normal file
28
app/DoctrineMigrations/Version20190227233244.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20190227233244 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('CREATE UNIQUE INDEX unique_post_id_comment_number ON posts.comments (post_id, number)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('DROP INDEX posts.unique_post_id_comment_number');
|
||||
}
|
||||
}
|
|
@ -71,6 +71,13 @@ doctrine_migrations:
|
|||
table_name: migration_versions
|
||||
name: Application Migrations
|
||||
|
||||
leezy_pheanstalk:
|
||||
pheanstalks:
|
||||
primary:
|
||||
server: "%beanstalkd_host%"
|
||||
port: "%beanstalkd_port%"
|
||||
default: true
|
||||
|
||||
# Swiftmailer Configuration
|
||||
swiftmailer:
|
||||
transport: "%mailer_transport%"
|
||||
|
|
|
@ -6,6 +6,11 @@ parameters:
|
|||
database_user: point
|
||||
database_password: ~
|
||||
|
||||
# Message Queue settings
|
||||
beanstalkd_host: 'localhost'
|
||||
beanstalkd_port: 11300
|
||||
beanstalkd_ws_updates_tube: 'point-websocket-updates'
|
||||
|
||||
mailer_transport: smtp
|
||||
mailer_host: 127.0.0.1
|
||||
mailer_user: ~
|
||||
|
|
|
@ -101,6 +101,13 @@ services:
|
|||
# Send message
|
||||
Skobkin\Bundle\PointToolsBundle\Command\TelegramSendMessageCommand:
|
||||
tags: [{ name: console.command }]
|
||||
# WebSocket MQ processing
|
||||
Skobkin\Bundle\PointToolsBundle\Command\ProcessWebsocketUpdatesCommand:
|
||||
arguments:
|
||||
$bsClient: '@leezy.pheanstalk.primary'
|
||||
$bsTubeName: '%beanstalkd_ws_updates_tube%'
|
||||
tags:
|
||||
- { name: console.command }
|
||||
|
||||
|
||||
# Entity repositories as services
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
"unreal4u/telegram-api": "^2.2",
|
||||
"csa/guzzle-bundle": "^3",
|
||||
"symfony/web-server-bundle": "^3.3",
|
||||
"sentry/sentry-symfony": "^2.2"
|
||||
"sentry/sentry-symfony": "^2.2",
|
||||
"leezy/pheanstalk-bundle": "^3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^3.0",
|
||||
|
|
110
composer.lock
generated
110
composer.lock
generated
|
@ -2273,6 +2273,66 @@
|
|||
],
|
||||
"time": "2018-05-16T12:15:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "leezy/pheanstalk-bundle",
|
||||
"version": "3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/armetiz/LeezyPheanstalkBundle.git",
|
||||
"reference": "76056c91c4021356b1bd4870f6bcd30d612d358d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/armetiz/LeezyPheanstalkBundle/zipball/76056c91c4021356b1bd4870f6bcd30d612d358d",
|
||||
"reference": "76056c91c4021356b1bd4870f6bcd30d612d358d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"pda/pheanstalk": "~3.0",
|
||||
"php": ">=5.5.9",
|
||||
"psr/log": "~1.0",
|
||||
"symfony/console": "~2.5|~3.0|^4.0",
|
||||
"symfony/framework-bundle": "~2.5|~3.0|^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0|~3.4"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Leezy\\PheanstalkBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Thomas Tourlourat",
|
||||
"email": "thomas@tourlourat.com",
|
||||
"homepage": "http://www.armetiz.info"
|
||||
}
|
||||
],
|
||||
"description": "The LeezyPheanstalkBundle is a Symfony2 Bundle that provides a command line interface for manage the Beanstalkd workqueue server & a pheanstalk integration.",
|
||||
"homepage": "https://github.com/armetiz/LeezyPheanstalkBundle",
|
||||
"keywords": [
|
||||
"asynchronous",
|
||||
"beanstalkd",
|
||||
"bundle",
|
||||
"messaging",
|
||||
"pheanstalk",
|
||||
"queueing",
|
||||
"symfony"
|
||||
],
|
||||
"time": "2018-03-02T15:28:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "michelf/php-markdown",
|
||||
"version": "1.8.0",
|
||||
|
@ -2623,6 +2683,56 @@
|
|||
],
|
||||
"time": "2018-07-02T15:55:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pda/pheanstalk",
|
||||
"version": "v3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pda/pheanstalk.git",
|
||||
"reference": "430e77c551479aad0c6ada0450ee844cf656a18b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pda/pheanstalk/zipball/430e77c551479aad0c6ada0450ee844cf656a18b",
|
||||
"reference": "430e77c551479aad0c6ada0450ee844cf656a18b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pheanstalk\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Annesley",
|
||||
"email": "paul@annesley.cc",
|
||||
"homepage": "http://paul.annesley.cc/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP client for beanstalkd queue",
|
||||
"homepage": "https://github.com/pda/pheanstalk",
|
||||
"keywords": [
|
||||
"beanstalkd"
|
||||
],
|
||||
"time": "2015-08-07T21:42:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpcollection/phpcollection",
|
||||
"version": "0.5.0",
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Skobkin\Bundle\PointToolsBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use JMS\Serializer\Serializer;
|
||||
use Leezy\PheanstalkBundle\Proxy\PheanstalkProxy;
|
||||
use Pheanstalk\Job;
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket\Message;
|
||||
use Skobkin\Bundle\PointToolsBundle\Exception\WebSocket\UnsupportedTypeException;
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\WebSocket\WebSocketMessageProcessor;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\{InputInterface, InputOption};
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* This command processes WebSocket updates MQ and stores new content in the DB
|
||||
*/
|
||||
class ProcessWebsocketUpdatesCommand extends Command
|
||||
{
|
||||
/** @var EntityManagerInterface */
|
||||
private $em;
|
||||
|
||||
/** @var PheanstalkProxy */
|
||||
private $bsClient;
|
||||
|
||||
/** @var string */
|
||||
private $bsTubeName;
|
||||
|
||||
/** @var Serializer */
|
||||
private $serializer;
|
||||
|
||||
/** @var WebSocketMessageProcessor */
|
||||
private $messageProcessor;
|
||||
|
||||
/** @var \Raven_Client */
|
||||
private $sentryClient;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
\Raven_Client $raven,
|
||||
PheanstalkProxy $bsClient,
|
||||
string $bsTubeName,
|
||||
Serializer $serializer,
|
||||
WebSocketMessageProcessor $processor
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->sentryClient = $raven;
|
||||
$this->serializer = $serializer;
|
||||
$this->messageProcessor = $processor;
|
||||
$this->bsClient = $bsClient;
|
||||
$this->bsTubeName = $bsTubeName;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/** {@inheritdoc} */
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('point:update:websocket-messages')
|
||||
->setDescription('Reads and processes updates from Beanstalkd queue pipe')
|
||||
->addOption('keep-jobs', 'k', InputOption::VALUE_NONE, 'Don\'t delete jobs from queue after processing')
|
||||
;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$keepJobs = (bool) $input->getOption('keep-jobs');
|
||||
|
||||
/** @var Job $job */
|
||||
while ($job = $this->bsClient->reserveFromTube($this->bsTubeName, 0)) {
|
||||
try {
|
||||
/** @var Message $message */
|
||||
$message = $this->serializer->deserialize($job->getData(), Message::class, 'json');
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(sprintf(
|
||||
'Error while deserializing #%d data: \'%s\'',
|
||||
$job->getId(),
|
||||
$job->getData()
|
||||
));
|
||||
$this->sentryClient->captureException($e);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->writeln('Processing job #'.$job->getId().' ('.$message->getA().')');
|
||||
|
||||
try {
|
||||
if ($this->messageProcessor->processMessage($message)) {
|
||||
$this->em->flush();
|
||||
|
||||
if (!$keepJobs) {
|
||||
$this->bsClient->delete($job);
|
||||
}
|
||||
|
||||
$this->em->clear();
|
||||
}
|
||||
} catch (UnsupportedTypeException $e) {
|
||||
$output->writeln(' Unsupported message type: '.$message->getA());
|
||||
$this->sentryClient->captureException($e);
|
||||
|
||||
continue;
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(' Message processing error: '.$e->getMessage());
|
||||
$this->sentryClient->captureException($e);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,39 +4,22 @@ namespace Skobkin\Bundle\PointToolsBundle\DTO\Api;
|
|||
|
||||
class Comment implements ValidableInterface
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $postId;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
/** @var int|null */
|
||||
private $number;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
/** @var int|null */
|
||||
private $toCommentId;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $created;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
/** @var User|null */
|
||||
private $author;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
/** @var bool|null */
|
||||
private $isRec;
|
||||
|
||||
|
||||
|
|
|
@ -4,27 +4,17 @@ namespace Skobkin\Bundle\PointToolsBundle\DTO\Api;
|
|||
|
||||
class MetaPost implements ValidableInterface
|
||||
{
|
||||
/**
|
||||
* @var Post|null
|
||||
*/
|
||||
/** @var Post|null */
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* @var Comment[]|null
|
||||
*/
|
||||
/** @var Comment[]|null */
|
||||
private $comments;
|
||||
|
||||
|
||||
public function getPost(): ?Post
|
||||
{
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
public function setPost(?Post $post): void
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Comment[]|null
|
||||
*/
|
||||
|
@ -33,20 +23,8 @@ class MetaPost implements ValidableInterface
|
|||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Comment[]|null $comments
|
||||
*/
|
||||
public function setComments(?array $comments): void
|
||||
{
|
||||
$this->comments = $comments;
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
if (null !== $this->post && $this->post->isValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return (null !== $this->post && $this->post->isValid());
|
||||
}
|
||||
}
|
|
@ -4,44 +4,28 @@ namespace Skobkin\Bundle\PointToolsBundle\DTO\Api;
|
|||
|
||||
class Post implements ValidableInterface
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string[]|null
|
||||
*/
|
||||
/** @var string[]|null */
|
||||
private $tags;
|
||||
|
||||
/**
|
||||
* @var string[]|null
|
||||
*/
|
||||
/** @var string[]|null */
|
||||
private $files;
|
||||
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
/** @var User|null */
|
||||
private $author;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $created;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
/** @var bool|null */
|
||||
private $private;
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
|
||||
namespace Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket;
|
||||
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\ValidableInterface;
|
||||
use Skobkin\Bundle\PointToolsBundle\Exception\WebSocket\UnsupportedTypeException;
|
||||
|
||||
/**
|
||||
* WebSocket update message
|
||||
*/
|
||||
class Message implements ValidableInterface
|
||||
{
|
||||
public const TYPE_COMMENT = 'comment';
|
||||
public const TYPE_COMMENT_EDITED = 'comment_edited';
|
||||
public const TYPE_POST = 'post';
|
||||
public const TYPE_POST_EDITED = 'post_edited';
|
||||
public const TYPE_POST_COMMENT_RECOMMENDATION = 'rec';
|
||||
public const TYPE_RECOMMENDATION_WITH_COMMENT = 'ok';
|
||||
|
||||
/**
|
||||
* Event type. @see Message::TYPE_* constants
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $a;
|
||||
|
||||
/**
|
||||
* Login of the user
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $author;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $authorId;
|
||||
|
||||
/** @var string|null */
|
||||
private $authorName;
|
||||
|
||||
/**
|
||||
* Number of the comment in the thread
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $commentId;
|
||||
|
||||
/**
|
||||
* ???
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
private $cut;
|
||||
|
||||
/**
|
||||
* Array of file paths
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
private $files;
|
||||
|
||||
/** @var string|null */
|
||||
private $html;
|
||||
|
||||
/**
|
||||
* @deprecated Link in the Post::type=feed posts
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $link;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $postAuthorId;
|
||||
|
||||
/** @var string */
|
||||
private $postId;
|
||||
|
||||
/** @var bool|null */
|
||||
private $private;
|
||||
|
||||
/**
|
||||
* Number of the comment in the thread for recommendation with text
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $rcid;
|
||||
|
||||
/**
|
||||
* Array of tags
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
private $tags;
|
||||
|
||||
/** @var string */
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @deprecated ???
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* Number of the comment to which this comment is answering
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $toCommentId;
|
||||
|
||||
/**
|
||||
* Text quotation of the comment to which this comment is answering
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $toText;
|
||||
|
||||
/**
|
||||
* Array of logins of users to which post is addressed
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
private $toUsers;
|
||||
|
||||
public function isPost(): bool
|
||||
{
|
||||
return self::TYPE_POST === $this->a;
|
||||
}
|
||||
|
||||
public function isComment(): bool
|
||||
{
|
||||
return self::TYPE_COMMENT === $this->a;
|
||||
}
|
||||
|
||||
public function isCommentRecommendation(): bool
|
||||
{
|
||||
return self::TYPE_RECOMMENDATION_WITH_COMMENT === $this->a;
|
||||
}
|
||||
|
||||
public function isPostRecommendation(): bool
|
||||
{
|
||||
return self::TYPE_POST_COMMENT_RECOMMENDATION === $this->a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
* @throws UnsupportedTypeException
|
||||
*/
|
||||
public function isValid(): bool
|
||||
{
|
||||
switch ($this->a) {
|
||||
case self::TYPE_POST:
|
||||
return $this->isValidPost();
|
||||
break;
|
||||
|
||||
case self::TYPE_POST_EDITED:
|
||||
return $this->isValidPostEdited();
|
||||
break;
|
||||
|
||||
case self::TYPE_COMMENT;
|
||||
return $this->isValidComment();
|
||||
break;
|
||||
|
||||
case self::TYPE_COMMENT_EDITED;
|
||||
return $this->isValidCommentEdited();
|
||||
break;
|
||||
|
||||
case self::TYPE_RECOMMENDATION_WITH_COMMENT;
|
||||
return $this->isValidRecommendationWithComment();
|
||||
break;
|
||||
|
||||
case self::TYPE_POST_COMMENT_RECOMMENDATION;
|
||||
return $this->isValidPostCommentRecommendation();
|
||||
break;
|
||||
|
||||
case null:
|
||||
throw new \RuntimeException('Message has NULL type.');
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedTypeException(sprintf('Type \'%s\' is not supported.', $this->a));
|
||||
}
|
||||
}
|
||||
|
||||
public function getA(): string
|
||||
{
|
||||
return $this->a;
|
||||
}
|
||||
|
||||
public function getAuthor(): string
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function getAuthorId(): int
|
||||
{
|
||||
return $this->authorId;
|
||||
}
|
||||
|
||||
public function getAuthorName(): ?string
|
||||
{
|
||||
return $this->authorName;
|
||||
}
|
||||
|
||||
public function getCommentId(): ?int
|
||||
{
|
||||
return $this->commentId;
|
||||
}
|
||||
|
||||
public function getCut(): ?bool
|
||||
{
|
||||
return $this->cut;
|
||||
}
|
||||
|
||||
public function getFiles(): ?array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
public function getHtml(): ?string
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
public function getLink(): ?string
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
public function getPostId(): string
|
||||
{
|
||||
return $this->postId;
|
||||
}
|
||||
|
||||
public function getPostAuthorId(): ?int
|
||||
{
|
||||
return $this->postAuthorId;
|
||||
}
|
||||
|
||||
public function getPrivate(): ?bool
|
||||
{
|
||||
return $this->private;
|
||||
}
|
||||
|
||||
public function getRcid(): ?int
|
||||
{
|
||||
return $this->rcid;
|
||||
}
|
||||
|
||||
public function getTags(): ?array
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getToCommentId(): ?string
|
||||
{
|
||||
return $this->toCommentId;
|
||||
}
|
||||
|
||||
public function getToText(): ?string
|
||||
{
|
||||
return $this->toText;
|
||||
}
|
||||
|
||||
public function getToUsers(): ?array
|
||||
{
|
||||
return $this->toUsers;
|
||||
}
|
||||
|
||||
private function hasCommonMandatoryData(): bool
|
||||
{
|
||||
return (
|
||||
null !== $this->author &&
|
||||
null !== $this->authorId &&
|
||||
null !== $this->postId
|
||||
);
|
||||
}
|
||||
|
||||
private function isValidPost(): bool
|
||||
{
|
||||
return $this->hasCommonMandatoryData() && (
|
||||
// Text can be empty ("") though
|
||||
null !== $this->text &&
|
||||
null !== $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
private function isValidPostEdited(): bool
|
||||
{
|
||||
return $this->isValidPost();
|
||||
}
|
||||
|
||||
private function isValidComment(): bool
|
||||
{
|
||||
return $this->hasCommonMandatoryData() && (
|
||||
null !== $this->commentId &&
|
||||
null !== $this->html &&
|
||||
null !== $this->text
|
||||
);
|
||||
}
|
||||
|
||||
private function isValidCommentEdited(): bool
|
||||
{
|
||||
return $this->hasCommonMandatoryData() && (null !== $this->commentId);
|
||||
}
|
||||
|
||||
private function isValidRecommendationWithComment(): bool
|
||||
{
|
||||
return $this->hasCommonMandatoryData();
|
||||
}
|
||||
|
||||
private function isValidPostCommentRecommendation(): bool
|
||||
{
|
||||
return $this->hasCommonMandatoryData() && (null !== $this->postAuthorId);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ use Skobkin\Bundle\PointToolsBundle\Entity\User;
|
|||
|
||||
class LoadCommentsData extends AbstractFixture implements OrderedFixtureInterface
|
||||
{
|
||||
public function load(ObjectManager $om)
|
||||
public function load(ObjectManager $om): void
|
||||
{
|
||||
/** @var Post $post */
|
||||
$post = $this->getReference('test_post_longpost');
|
||||
|
@ -25,29 +25,28 @@ class LoadCommentsData extends AbstractFixture implements OrderedFixtureInterfac
|
|||
$this->getReference('test_user_99995'),
|
||||
];
|
||||
|
||||
$comments = [];
|
||||
$text = 'Some text with [link to @skobkin-ru site](https://skobk.in/) and `code block`'.PHP_EOL.
|
||||
'and some quotation:'.PHP_EOL.
|
||||
'> test test quote'.PHP_EOL.
|
||||
'and some text after';
|
||||
|
||||
foreach (range(1, 10000) as $num) {
|
||||
$comment = (new Comment())
|
||||
->setNumber($num)
|
||||
->setDeleted(mt_rand(0, 15) ? false : true)
|
||||
->setCreatedAt(new \DateTime())
|
||||
->setAuthor($users[array_rand($users)])
|
||||
->setRec(false)
|
||||
->setText(
|
||||
'Some text with [link to @skobkin-ru site](https://skobk.in/) and `code block`'.PHP_EOL.
|
||||
'and some quotation:'.PHP_EOL.
|
||||
'> test test quote'.PHP_EOL.
|
||||
'and some text after'
|
||||
)
|
||||
;
|
||||
$comment = new Comment(
|
||||
$text,
|
||||
new \DateTime(),
|
||||
false,
|
||||
$post,
|
||||
$num,
|
||||
($num > 1 && !random_int(0, 4)) ? random_int(1, $num - 1) : null,
|
||||
$users[array_rand($users)],
|
||||
[]
|
||||
);
|
||||
|
||||
if (count($comments) > 0 && mt_rand(0, 1)) {
|
||||
$comment->setParent($comments[mt_rand(0, count($comments) - 1)]);
|
||||
if (!random_int(0, 15)) {
|
||||
$comment->delete();
|
||||
}
|
||||
|
||||
$post->addComment($comment);
|
||||
$comments[] = $comment;
|
||||
|
||||
$om->persist($comment);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use Skobkin\Bundle\PointToolsBundle\Entity\User;
|
|||
/**
|
||||
* @ORM\Table(name="comments", schema="posts", indexes={
|
||||
* @ORM\Index(name="idx_comment_created_at", columns={"created_at"})
|
||||
* }, uniqueConstraints={
|
||||
* @ORM\UniqueConstraint(name="unique_post_id_comment_number", columns={"post_id", "number"})
|
||||
* })
|
||||
* @ORM\Entity(repositoryClass="Skobkin\Bundle\PointToolsBundle\Repository\Blogs\CommentRepository")
|
||||
*/
|
||||
|
@ -62,10 +64,17 @@ class Comment
|
|||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="number", type="smallint")
|
||||
* @ORM\Column(name="number", type="integer", unique=true)
|
||||
*/
|
||||
private $number;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*
|
||||
* @ORM\Column(name="to_number", type="integer", nullable=true)
|
||||
*/
|
||||
private $toNumber;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
|
@ -85,26 +94,36 @@ class Comment
|
|||
*/
|
||||
private $files;
|
||||
|
||||
/**
|
||||
* @var Comment|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Skobkin\Bundle\PointToolsBundle\Entity\Blogs\Comment", inversedBy="children")
|
||||
* @ORM\JoinColumn(name="parent_id", nullable=true)
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* @var Comment[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="Skobkin\Bundle\PointToolsBundle\Entity\Blogs\Comment", fetch="EXTRA_LAZY", mappedBy="parent")
|
||||
*/
|
||||
private $children;
|
||||
public function __construct(
|
||||
string $text,
|
||||
\DateTime $createdAt,
|
||||
bool $rec,
|
||||
Post $post,
|
||||
int $number,
|
||||
?int $toNumber,
|
||||
User $author,
|
||||
array $files
|
||||
) {
|
||||
$this->text = $text;
|
||||
$this->createdAt = $createdAt;
|
||||
$this->rec = $rec;
|
||||
$this->post = $post;
|
||||
$this->number = $number;
|
||||
$this->toNumber = $toNumber;
|
||||
$this->author = $author;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->files = new ArrayCollection();
|
||||
$this->children = new ArrayCollection();
|
||||
foreach ($files as $file) {
|
||||
if (!($file instanceof File)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'$files array must contain only \'%s\' objects. %s given.',
|
||||
\is_object($file) ? \get_class($file) : \gettype($file)
|
||||
));
|
||||
}
|
||||
|
||||
$this->files->add($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
|
@ -112,95 +131,41 @@ class Comment
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
public function setCreatedAt(\DateTime $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTime
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setText(string $text): self
|
||||
{
|
||||
$this->text = $text;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function setRec(bool $rec): self
|
||||
{
|
||||
$this->rec = $rec;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isRec(): bool
|
||||
{
|
||||
return $this->rec;
|
||||
}
|
||||
|
||||
public function getRec(): bool
|
||||
{
|
||||
return $this->rec;
|
||||
}
|
||||
|
||||
public function getPost(): Post
|
||||
{
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
public function setPost(Post $post): self
|
||||
{
|
||||
$this->post = $post;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNumber(int $number): self
|
||||
{
|
||||
$this->number = $number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNumber(): int
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
public function getToNumber(): ?int
|
||||
{
|
||||
return $this->toNumber;
|
||||
}
|
||||
|
||||
public function getAuthor(): User
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function setAuthor(User $author): self
|
||||
{
|
||||
$this->author = $author;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addFile(File $files): self
|
||||
{
|
||||
$this->files[] = $files;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeFile(File $files): void
|
||||
{
|
||||
$this->files->removeElement($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File[]|ArrayCollection
|
||||
*/
|
||||
|
@ -209,52 +174,22 @@ class Comment
|
|||
return $this->files;
|
||||
}
|
||||
|
||||
public function getParent(): ?Comment
|
||||
public function delete(): self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(Comment $parent): self
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->deleted = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDeleted(bool $deleted): self
|
||||
public function restore(): self
|
||||
{
|
||||
$this->deleted = $deleted;
|
||||
$this->deleted = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDeleted(): bool
|
||||
{
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function isDeleted(): bool
|
||||
{
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function addChild(Comment $children): self
|
||||
{
|
||||
$this->children[] = $children;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(Comment $children): void
|
||||
{
|
||||
$this->children->removeElement($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Comment[]|ArrayCollection
|
||||
*/
|
||||
public function getChildren(): iterable
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,13 @@ class Post
|
|||
*/
|
||||
private $deleted = false;
|
||||
|
||||
/**
|
||||
* @var bool Status of point-tools subscription to the post (to receive WS updates)
|
||||
*
|
||||
* @ORM\Column(name="is_subscribed", type="boolean", options={"default": false})
|
||||
*/
|
||||
private $subscribed = false;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
|
@ -103,7 +110,7 @@ class Post
|
|||
private $comments;
|
||||
|
||||
|
||||
public function __construct(string $id, User $author, \DateTime $createdAt, string $type)
|
||||
public function __construct(string $id, User $author, \DateTime $createdAt, string $type = self::TYPE_POST)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->author = $author;
|
||||
|
@ -216,6 +223,25 @@ class Post
|
|||
{
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function isSubscribed(): bool
|
||||
{
|
||||
return $this->subscribed;
|
||||
}
|
||||
|
||||
public function subscribe(): self
|
||||
{
|
||||
$this->subscribed = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unsubscribe(): self
|
||||
{
|
||||
$this->subscribed = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPrivate(bool $private): self
|
||||
{
|
||||
|
@ -237,7 +263,6 @@ class Post
|
|||
public function addComment(Comment $comment): self
|
||||
{
|
||||
$this->comments[] = $comment;
|
||||
$comment->setPost($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ use Skobkin\Bundle\PointToolsBundle\Entity\User;
|
|||
|
||||
/**
|
||||
* @ORM\Table(name="telegram_accounts", schema="users", indexes={
|
||||
* @ORM\Index(name="subscriber_notification_idx", columns={"subscriber_notification"}, options={"where": "subscriber_notification = TRUE"}),
|
||||
* @ORM\Index(name="rename_notification_idx", columns={"rename_notification"}, options={"where": "rename_notification = TRUE"}),
|
||||
* @ORM\Index(name="subscriber_notification_idx", columns={"subscriber_notification"}, options={"where": "(subscriber_notification = true)"}),
|
||||
* @ORM\Index(name="rename_notification_idx", columns={"rename_notification"}, options={"where": "(rename_notification = true)"}),
|
||||
* })
|
||||
* @ORM\Entity(repositoryClass="Skobkin\Bundle\PointToolsBundle\Repository\Telegram\AccountRepository")
|
||||
* @ORM\HasLifecycleCallbacks()
|
||||
|
|
|
@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||
|
||||
/**
|
||||
* @ORM\Table(name="users", schema="users", indexes={
|
||||
* @ORM\Index(name="idx_user_login", columns={"login"}),
|
||||
* @ORM\Index(name="idx_user_public", columns={"public"}),
|
||||
* @ORM\Index(name="idx_user_removed", columns={"is_removed"})
|
||||
* })
|
||||
|
@ -190,15 +191,24 @@ class User
|
|||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function updateCreatedAt(\DateTime $date): self
|
||||
{
|
||||
$this->createdAt = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTime
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function updatePrivacy(?bool $public, ?bool $whitelistOnly): void
|
||||
public function updatePrivacy(bool $public, bool $whitelistOnly): self
|
||||
{
|
||||
$this->public = $public;
|
||||
$this->whitelistOnly = $whitelistOnly;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPublic(): ?bool
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Skobkin\Bundle\PointToolsBundle\Exception\Api;
|
||||
|
||||
class PostNotFoundException extends NotFoundException
|
||||
{
|
||||
/** @var string */
|
||||
private $id;
|
||||
|
||||
public function __construct(string $id, \Exception $previous)
|
||||
{
|
||||
parent::__construct('Post not found', 0, $previous);
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
|
@ -4,16 +4,11 @@ namespace Skobkin\Bundle\PointToolsBundle\Exception\Api;
|
|||
|
||||
class UserNotFoundException extends NotFoundException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $userId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $login;
|
||||
/** @var int */
|
||||
private $userId;
|
||||
|
||||
/** @var string */
|
||||
private $login;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Skobkin\Bundle\PointToolsBundle\Exception\WebSocket;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class UnsupportedTypeException extends RuntimeException
|
||||
{
|
||||
}
|
|
@ -2,10 +2,6 @@ Skobkin\Bundle\PointToolsBundle\DTO\Api\Comment:
|
|||
exclusion_policy: none
|
||||
access_type: public_method
|
||||
properties:
|
||||
postId:
|
||||
serialized_name: 'post_id'
|
||||
type: 'Skobkin\Bundle\PointToolsBundle\DTO\Api\Post'
|
||||
max_depth: 2
|
||||
number:
|
||||
serialized_name: 'id'
|
||||
type: integer
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
Skobkin\Bundle\PointToolsBundle\DTO\Api\MetaPost:
|
||||
exclusion_policy: none
|
||||
access_type: public_method
|
||||
properties:
|
||||
post:
|
||||
serialized_name: 'post'
|
||||
type: 'Skobkin\Bundle\PointToolsBundle\DTO\Api\Post'
|
||||
max_depth: 2
|
||||
comments:
|
||||
serialized_name: 'comments'
|
||||
type: 'array<Skobkin\Bundle\PointToolsBundle\DTO\Api\Comment>'
|
||||
max_depth: 2
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket\Message:
|
||||
exclusion_policy: none
|
||||
properties:
|
||||
a:
|
||||
type: string
|
||||
serialized_name: 'a'
|
||||
author:
|
||||
type: string
|
||||
serialized_name: 'author'
|
||||
authorId:
|
||||
type: int
|
||||
serialized_name: 'author_id'
|
||||
authorName:
|
||||
type: string
|
||||
serialized_name: 'author_name'
|
||||
commentId:
|
||||
type: int
|
||||
serialized_name: 'comment_id'
|
||||
cut:
|
||||
type: bool
|
||||
serialized_name: 'cut'
|
||||
files:
|
||||
type: array<string>
|
||||
serialized_name: 'files'
|
||||
html:
|
||||
type: string
|
||||
serialized_name: 'html'
|
||||
link:
|
||||
type: string
|
||||
serialized_name: 'link'
|
||||
postId:
|
||||
type: string
|
||||
serialized_name: 'post_id'
|
||||
postAuthorId:
|
||||
type: int
|
||||
serialized_name: 'post_author_id'
|
||||
private:
|
||||
type: bool
|
||||
serialized_name: 'private'
|
||||
rcid:
|
||||
type: int
|
||||
serialized_name: 'rcid'
|
||||
tags:
|
||||
type: array<string>
|
||||
serialized_name: 'tags'
|
||||
text:
|
||||
type: string
|
||||
serialized_name: 'text'
|
||||
title:
|
||||
type: string
|
||||
serialized_name: 'title'
|
||||
toCommentId:
|
||||
type: string
|
||||
serialized_name: 'to_comment_id'
|
||||
toText:
|
||||
type: string
|
||||
serialized_name: 'to_text'
|
||||
toUsers:
|
||||
type: array<string>
|
||||
serialized_name: 'to_users'
|
|
@ -5,6 +5,9 @@ namespace Skobkin\Bundle\PointToolsBundle\Service\Api;
|
|||
use GuzzleHttp\ClientInterface;
|
||||
use JMS\Serializer\SerializerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\MetaPost;
|
||||
use Skobkin\Bundle\PointToolsBundle\Entity\Blogs\Post;
|
||||
use Skobkin\Bundle\PointToolsBundle\Exception\Api\{NotFoundException, PostNotFoundException};
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\Factory\Blogs\PostFactory;
|
||||
|
||||
/**
|
||||
|
@ -12,6 +15,8 @@ use Skobkin\Bundle\PointToolsBundle\Service\Factory\Blogs\PostFactory;
|
|||
*/
|
||||
class PostApi extends AbstractApi
|
||||
{
|
||||
private const PREFIX = '/api/post/';
|
||||
|
||||
/**
|
||||
* @var PostFactory
|
||||
*/
|
||||
|
@ -24,4 +29,24 @@ class PostApi extends AbstractApi
|
|||
|
||||
$this->postFactory = $postFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PostNotFoundException
|
||||
*/
|
||||
public function getById(string $id): Post
|
||||
{
|
||||
try {
|
||||
$postData = $this->getGetJsonData(
|
||||
self::PREFIX.$id,
|
||||
[],
|
||||
MetaPost::class
|
||||
);
|
||||
} catch (NotFoundException $e) {
|
||||
throw new PostNotFoundException($id, $e);
|
||||
}
|
||||
|
||||
// Not catching ForbiddenException right now
|
||||
|
||||
return $this->postFactory->findOrCreateFromDtoWithContent($postData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
namespace Skobkin\Bundle\PointToolsBundle\Service\Api;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use JMS\Serializer\{
|
||||
DeserializationContext, SerializerInterface
|
||||
};
|
||||
use JMS\Serializer\{DeserializationContext, SerializerInterface};
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\{Auth, User as UserDTO};
|
||||
use Skobkin\Bundle\PointToolsBundle\Entity\User;
|
||||
|
|
|
@ -6,6 +6,8 @@ use Psr\Log\LoggerInterface;
|
|||
|
||||
abstract class AbstractFactory
|
||||
{
|
||||
public const DATE_FORMAT = 'Y-m-d_H:i:s';
|
||||
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
namespace Skobkin\Bundle\PointToolsBundle\Service\Factory\Blogs;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket\Message;
|
||||
use Skobkin\Bundle\PointToolsBundle\Entity\Blogs\Comment;
|
||||
use Skobkin\Bundle\PointToolsBundle\Repository\Blogs\{CommentRepository, PostRepository};
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\Api\PostApi;
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\Factory\{AbstractFactory, UserFactory};
|
||||
|
||||
class CommentFactory extends AbstractFactory
|
||||
|
@ -17,12 +20,45 @@ class CommentFactory extends AbstractFactory
|
|||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/** @var PostApi */
|
||||
private $postApi;
|
||||
|
||||
public function __construct(LoggerInterface $logger, CommentRepository $commentRepository, PostRepository $postRepository, UserFactory $userFactory)
|
||||
|
||||
public function __construct(LoggerInterface $logger, CommentRepository $commentRepository, PostRepository $postRepository, UserFactory $userFactory, PostApi $postApi)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->userFactory = $userFactory;
|
||||
$this->commentRepository = $commentRepository;
|
||||
$this->postRepository = $postRepository;
|
||||
$this->postApi = $postApi;
|
||||
}
|
||||
|
||||
public function findOrCreateFromWebsocketMessage(Message $message): Comment
|
||||
{
|
||||
if ($message->isValid()) {
|
||||
throw new \InvalidArgumentException('Comment is invalid');
|
||||
}
|
||||
if ($message->isComment()) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid Message object provided. %s expected, %s given',
|
||||
Message::TYPE_COMMENT,
|
||||
$message->getA()
|
||||
));
|
||||
}
|
||||
|
||||
if (null === $comment = $this->commentRepository->findOneBy(['post' => $post, 'number' => $message->getCommentId()])) {
|
||||
$author = $this->userFactory->findOrCreateFromIdLoginAndName(
|
||||
$message->getAuthorId(),
|
||||
$message->getAuthor(),
|
||||
$message->getAuthorName()
|
||||
);
|
||||
|
||||
if (null === $post = $this->postRepository->find($message->getPostId())) {
|
||||
$post = $this->postApi->getById($message->getPostId());
|
||||
}
|
||||
|
||||
// TODO
|
||||
//$comment = new Comment()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,11 @@ namespace Skobkin\Bundle\PointToolsBundle\Service\Factory\Blogs;
|
|||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\{MetaPost, Post as PostDTO, PostsPage};
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\{MetaPost, Post as ApiPost, PostsPage};
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket\Message as WebsocketMessage;
|
||||
use Skobkin\Bundle\PointToolsBundle\Entity\{Blogs\Post, Blogs\PostTag, User};
|
||||
use Skobkin\Bundle\PointToolsBundle\Exception\{Api\InvalidResponseException, Factory\Blog\InvalidDataException};
|
||||
use Skobkin\Bundle\PointToolsBundle\Repository\Blogs\PostRepository;
|
||||
use Skobkin\Bundle\PointToolsBundle\Repository\{Blogs\PostRepository, UserRepository};
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\Factory\{AbstractFactory, UserFactory};
|
||||
|
||||
class PostFactory extends AbstractFactory
|
||||
|
@ -18,6 +19,9 @@ class PostFactory extends AbstractFactory
|
|||
/** @var PostRepository */
|
||||
private $postRepository;
|
||||
|
||||
/** @var UserRepository */
|
||||
private $userRepository;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
|
@ -35,6 +39,7 @@ class PostFactory extends AbstractFactory
|
|||
LoggerInterface $logger,
|
||||
EntityManagerInterface $em,
|
||||
PostRepository $postRepository,
|
||||
UserRepository $userRepository,
|
||||
UserFactory $userFactory,
|
||||
FileFactory $fileFactory,
|
||||
CommentFactory $commentFactory,
|
||||
|
@ -43,6 +48,7 @@ class PostFactory extends AbstractFactory
|
|||
parent::__construct($logger);
|
||||
$this->em = $em;
|
||||
$this->postRepository = $postRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->userFactory = $userFactory;
|
||||
$this->fileFactory = $fileFactory;
|
||||
$this->commentFactory = $commentFactory;
|
||||
|
@ -92,8 +98,6 @@ class PostFactory extends AbstractFactory
|
|||
/**
|
||||
* Create full post with tags, files and comments
|
||||
*
|
||||
* @todo Implement comments
|
||||
*
|
||||
* @throws InvalidDataException
|
||||
*/
|
||||
public function findOrCreateFromDtoWithContent(MetaPost $metaPost): Post
|
||||
|
@ -111,7 +115,7 @@ class PostFactory extends AbstractFactory
|
|||
throw $e;
|
||||
}
|
||||
|
||||
$post = $this->findOrCreateFromDto($postData, $author);
|
||||
$post = $this->findOrCreateFromApiDto($postData, $author);
|
||||
|
||||
try {
|
||||
$this->updatePostTags($post, $postData->getTags() ?: []);
|
||||
|
@ -127,10 +131,62 @@ class PostFactory extends AbstractFactory
|
|||
throw $e;
|
||||
}
|
||||
|
||||
// @TODO implement comments
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
private function findOrCreateFromDto(PostDTO $postData, User $author): Post
|
||||
public function findOrCreateFromWebsocketDto(WebsocketMessage $message): Post
|
||||
{
|
||||
if (!$message->isValid()) {
|
||||
throw new InvalidDataException('Invalid post data');
|
||||
}
|
||||
if (!$message->isPost()) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Incorrect message type received. \'post\' expected \'%s\' given',
|
||||
$message->getA()
|
||||
));
|
||||
}
|
||||
|
||||
if (null === $post = $this->postRepository->find($message->getPostId())) {
|
||||
$author = $this->userFactory->findOrCreateFromIdLoginAndName(
|
||||
$message->getAuthorId(),
|
||||
$message->getAuthor(),
|
||||
$message->getAuthorName()
|
||||
);
|
||||
|
||||
$post = new Post(
|
||||
$message->getPostId(),
|
||||
$author,
|
||||
new \DateTime(),
|
||||
Post::TYPE_POST
|
||||
);
|
||||
$this->postRepository->add($post);
|
||||
}
|
||||
|
||||
$post
|
||||
->setText($message->getText())
|
||||
->setPrivate((bool) $message->getPrivate())
|
||||
;
|
||||
|
||||
try {
|
||||
$this->updatePostTags($post, $message->getTags() ?: []);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error while updating post tags');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->updatePostFiles($post, $message->getFiles() ?: []);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error while updating post files');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
private function findOrCreateFromApiDto(ApiPost $postData, User $author): Post
|
||||
{
|
||||
if (null === ($post = $this->postRepository->find($postData->getId()))) {
|
||||
// Creating new post
|
||||
|
@ -145,7 +201,7 @@ class PostFactory extends AbstractFactory
|
|||
|
||||
$post
|
||||
->setText($postData->getText())
|
||||
->setPrivate($postData->getPrivate())
|
||||
->setPrivate((bool) $postData->getPrivate())
|
||||
;
|
||||
|
||||
return $post;
|
||||
|
|
|
@ -10,12 +10,9 @@ use Skobkin\Bundle\PointToolsBundle\Exception\Factory\InvalidUserDataException;
|
|||
|
||||
class UserFactory extends AbstractFactory
|
||||
{
|
||||
public const DATE_FORMAT = 'Y-m-d_H:i:s';
|
||||
|
||||
/** @var UserRepository */
|
||||
private $userRepository;
|
||||
|
||||
|
||||
public function __construct(LoggerInterface $logger, UserRepository $userRepository)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
@ -23,27 +20,25 @@ class UserFactory extends AbstractFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* @param UserDTO $userData
|
||||
*
|
||||
* @return User
|
||||
*
|
||||
* @throws InvalidUserDataException
|
||||
*/
|
||||
public function findOrCreateFromDTO(UserDTO $userData): User
|
||||
{
|
||||
// @todo LOG
|
||||
|
||||
if (!$userData->isValid()) {
|
||||
throw new InvalidUserDataException('Invalid user data', $userData);
|
||||
}
|
||||
|
||||
$createdAt = \DateTime::createFromFormat(self::DATE_FORMAT, $userData->getCreated()) ?: new \DateTime();
|
||||
|
||||
/** @var User $user */
|
||||
if (null === ($user = $this->userRepository->find($userData->getId()))) {
|
||||
$user = new User(
|
||||
$userData->getId(),
|
||||
\DateTime::createFromFormat('Y-m-d_H:i:s', $userData->getCreated()) ?: new \DateTime()
|
||||
$createdAt
|
||||
);
|
||||
$this->userRepository->add($user);
|
||||
} else {
|
||||
$user->updateCreatedAt($createdAt);
|
||||
}
|
||||
|
||||
$user->updateLoginAndName($userData->getLogin(), $userData->getName());
|
||||
|
@ -55,6 +50,21 @@ class UserFactory extends AbstractFactory
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function findOrCreateFromIdLoginAndName(int $id, string $login, ?string $name): User
|
||||
{
|
||||
/** @var User $user */
|
||||
if (null === $user = $this->userRepository->find($id)) {
|
||||
// We're using current date now but next time when we'll be updating user from API it'll be fixed
|
||||
$user = new User($id, new \DateTime(), $login, $name);
|
||||
$this->userRepository->add($user);
|
||||
} else {
|
||||
// @todo update login?
|
||||
// Probably don't because no name in the WS message (or maybe after PR to point?)
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Skobkin\Bundle\PointToolsBundle\Service\WebSocket;
|
||||
|
||||
use Skobkin\Bundle\PointToolsBundle\DTO\Api\WebSocket\Message;
|
||||
use Skobkin\Bundle\PointToolsBundle\Service\Factory\Blogs\{CommentFactory, PostFactory};
|
||||
|
||||
class WebSocketMessageProcessor
|
||||
{
|
||||
/** @var PostFactory */
|
||||
private $postFactory;
|
||||
|
||||
/** @var CommentFactory */
|
||||
private $commentFactory;
|
||||
|
||||
public function __construct(PostFactory $postFactory, CommentFactory $commentFactory)
|
||||
{
|
||||
$this->postFactory = $postFactory;
|
||||
$this->commentFactory = $commentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true on success (all data saved)
|
||||
*/
|
||||
public function processMessage(Message $message): bool
|
||||
{
|
||||
if (!$message->isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case $message->isPost():
|
||||
return $this->processPost($message);
|
||||
break;
|
||||
|
||||
case $message->isComment():
|
||||
return $this->processComment($message);
|
||||
break;
|
||||
|
||||
case $message->isCommentRecommendation():
|
||||
return $this->processRecommendation($message);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processPost(Message $postData): bool
|
||||
{
|
||||
$this->postFactory->findOrCreateFromWebsocketDto($postData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processComment(Message $commentData): bool
|
||||
{
|
||||
// Not done yet
|
||||
return false;
|
||||
|
||||
$this->commentFactory->findOrCreateFromWebsocketMessage($commentData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processRecommendation(Message $recommendData): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue