at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
@@ -0,0 +1,97 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractApiController;
use ApiBundle\Response\View;
use AppBundle\Response\SearchResponseInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class AbstractV1Controller
*
* @package AppBundle\Controller\V1
*
* @deprecated
* @see AbstractApiController
*/
abstract class AbstractV1Controller
{
/**
* Generate proper server response.
*
* @param mixed $data A data sent to client.
* @param integer $code Response http code.
* @param array $groups Serialization groups.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function generateResponse(
$data = null,
$code = null,
array $groups = []
) {
return new View($data, $groups, $code);
}
/**
* @param mixed $data A data for pagination.
* @param integer $page A requested page, starts from 1.
* @param integer $limit Required numbers of data per page.
*
* @return array
*/
protected function paginate($data, $page, $limit)
{
if ($data instanceof SearchResponseInterface) {
//
// Response from index or cache already paginated so we just return
// values.
//
return [
'data' => $data->getDocuments(),
'count' => count($data),
'totalCount' => $data->getTotalCount(),
'page' => $page,
'limit' => $limit,
];
} elseif ($data instanceof QueryBuilder) {
$data
->setMaxResults($limit)
->setFirstResult(($page - 1) * $limit);
$paginator = new Paginator($data);
$data = iterator_to_array($paginator);
return [
'data' => $data,
'count' => count($data),
'totalCount' => $paginator->count(),
'page' => $page,
'limit' => $limit,
];
}
return [];// TODO add code for over paginated data.
}
/**
* Returns a NotFoundHttpException.
*
* This will result in a 404 response code. Usage example:
*
* throw $this->createNotFoundException('Page not found!');
*
* @param string $message A message.
* @param \Exception|null $previous The previous exception.
*
* @return NotFoundHttpException
*/
protected function createNotFoundException($message = 'Not Found', \Exception $previous = null)
{
return new NotFoundHttpException($message, $previous);
}
}
@@ -0,0 +1,286 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractApiController;
use ApiBundle\Entity\ManageableEntityInterface;
use ApiBundle\Form\EntitiesBatchType;
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
use ApiBundle\Security\Inspector\InspectorInterface;
use AppBundle\Controller\Traits\AccessCheckerTrait;
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class AbstractV1CrudController
*
* @package AppBundle\Controller\V1
*
* @deprecated
* @see AbstractApiController
*/
abstract class AbstractV1CrudController extends AbstractV1Controller
{
use
FormFactoryAwareTrait,
AccessCheckerTrait;
/**
* @var EntityManagerInterface
*/
protected $em;
/**
* Entity fqcn.
*
* @var string
*/
protected $entity;
/**
* AbstractV1CrudController constructor.
*
* @param FormFactoryInterface $formFactory A FormFactoryInterface instance.
* @param AccessCheckerInterface $accessChecker A AccessCheckerInterface instance.
* @param EntityManagerInterface $em A EntityManagerInterface instance.
* @param string $entity A used entity name.
*/
public function __construct(
FormFactoryInterface $formFactory,
AccessCheckerInterface $accessChecker,
EntityManagerInterface $em,
$entity
) {
$this->formFactory = $formFactory;
$this->accessChecker = $accessChecker;
$this->em = $em;
$this->entity = $entity;
}
/**
* Create new entity.
*
* @param Request $request A Request instance.
* @param ManageableEntityInterface $entity A ManageableEntityInterface
* instance.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function createEntity(Request $request, ManageableEntityInterface $entity)
{
$form = $this->createForm($entity->getCreateFormClass(), $entity);
// Submit data into form.
$form->submit($request->request->all());
if ($form->isValid()) {
// Check that current user can create this entity.
// If user don't have rights to create this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::CREATE, $entity);
if (count($reasons) > 0) {
// User don't have rights to create this entity so send all
// founded restriction reasons to client.
return $this->generateResponse($reasons, 403);
}
$this->em->persist($entity);
$this->em->flush();
return $entity;
}
// Client send invalid data.
return $this->generateResponse($form, 400);
}
/**
* Get information about single entity.
*
* @param integer|ManageableEntityInterface|null $id A entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function getEntity($id)
{
$foundedEntity = $id;
if (is_numeric($id)) {
$repository = $this->em->getRepository($this->entity);
$foundedEntity = $repository->find($id);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$id}.", 404);
}
// Check that current user can read this entity.
// If user don't have rights to read this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::READ, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
return $foundedEntity;
}
/**
* Update entity.
*
* @param Request $request A Request instance.
* @param integer|ManageableEntityInterface|null $entity A entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
protected function putEntity(Request $request, $entity)
{
$foundedEntity = $entity;
if (is_numeric($entity)) {
$repository = $this->em->getRepository($this->entity);
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
$foundedEntity = $repository->find($entity);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
}
$form = $this->createForm($foundedEntity->getUpdateFormClass(), $foundedEntity, [
'method' => 'PUT',
]);
$form->submit($request->request->all());
if ($form->isValid()) {
// Check that current user can update this entity.
// If user don't have rights to update this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$this->em->persist($foundedEntity);
$this->em->flush();
return $foundedEntity;
}
return $this->generateResponse($form, 400);
}
/**
* Delete entity.
*
* @param integer|ManageableEntityInterface|null $entity A entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function deleteEntity($entity)
{
$foundedEntity = $entity;
if (is_numeric($entity)) {
$repository = $this->em->getRepository($this->entity);
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
$foundedEntity = $repository->find($entity);
}
if ($foundedEntity === null) {
$name = \app\c\getShortName($this->entity);
// Remove 'Abstract' prefix if it exists.
if (strpos($name, 'Abstract') !== false) {
$name = substr($name, 8);
}
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
}
// Check that current user can delete this entity.
// If user don't have rights to delete this entity we should send all
// founded restrictions to client.
$reasons = $this->checkAccess(InspectorInterface::DELETE, $foundedEntity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$this->em->remove($foundedEntity);
$this->em->flush();
return $this->generateResponse();
}
/**
* @param Request $request A Request instance.
* @param string|callable $permission A requested permission.
* @param string $formClass Form class fqcn.
* @param callable $processor Function which process founded entities.
*
* @return \ApiBundle\Response\ViewInterface
*/
protected function batchProcessing(
Request $request,
$permission,
$formClass,
callable $processor
) {
$this->checkFormClass($formClass);
$form = $this->createForm($formClass, null, [ 'class' => $this->entity ]);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
if (is_callable($permission)) {
$permission = call_user_func_array($permission, $data);
}
if (! is_string($permission)) {
throw new \InvalidArgumentException('$permission should be string or callable');
}
$reasons = $this->checkAccess($permission, $data['entities']);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$response = call_user_func_array($processor, $data);
if ($response === null) {
$response = $this->generateResponse();
}
return $response;
}
return $this->generateResponse($form, 400);
}
/**
* @param string $formClass Form class fqcn.
*
* @return void
*/
private function checkFormClass($formClass)
{
if (! is_string($formClass) || ! class_exists($formClass)) {
throw new \InvalidArgumentException('$formClass should be fqcn');
}
if (($formClass !== EntitiesBatchType::class)
&& ! in_array(EntitiesBatchType::class, class_parents($formClass), true)) {
throw new \InvalidArgumentException('Invalid form class '. $formClass);
}
}
}
@@ -0,0 +1,288 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractCRUDController;
use ApiBundle\Controller\Annotation\Roles;
use ApiDocBundle\Controller\Annotation\AppApiDoc;
use AppBundle\Exception\NotAllowedException;
use CacheBundle\DTO\AnalyticDTO;
use CacheBundle\Entity\Analytic\Analytic;
use CacheBundle\Form\AnalyticType;
use CacheBundle\Service\Factory\Analytic\AnalyticFactoryInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use ApiBundle\Security\Inspector\InspectorInterface;
use CacheBundle\Repository\AnalyticRepository;
/**
* Class AnalyticController
* @package AppBundle\Controller\V1
*
* @Route("/analysis", service="app.controller.analytic")
*/
class AnalyticController extends AbstractCRUDController
{
/**
* @var AnalyticFactoryInterface
*/
private $analyticFactory;
/**
* Create new analytic entity.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* methods={ "POST" }
* )
* @AppApiDoc(
* section="Analytic",
* resource=false,
* input={
* "class"="CacheBundle\Form\AnalyticType"
* },
* output={
* "class"="CacheBundle\Entity\Analytic\Analytic",
* "groups"={ "analytic", "id" }
* },
* statusCodes={
* 200="Analytics successfully created.",
* 400="Invalid data provided.",
* 403="You don't have permissions to create analytics."
* }
* )
*
* @param Request $request A Http Request instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function postAction(Request $request)
{
$user = $this->getCurrentUser();
$this->analyticFactory = $this->get('cache.analytic_factory');
$form = $this->createForm(AnalyticType::class)
->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var AnalyticDTO $dto */
$dto = $form->getData();
try {
$analytic = $this->analyticFactory->createAnalytic($dto, $user);
} catch (NotAllowedException $exception) {
return $this->generateResponse('You not allowed to make analytics');
}
$this->getManager()->persist($analytic);
$this->getManager()->flush();
return $this->generateResponse($analytic, 200, ['id', 'analytic']);
}
return $this->generateResponse($form, 400);
}
/**
* Get specified analytic by id.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{id}",
* requirements={ "id"="\d+" },
* methods={ "GET" }
* )
* @AppApiDoc(
* resource=true,
* section="Analytic",
* output={
* "class"="CacheBundle\Entity\Analytic\Analytic",
* "groups"={"id"}
* },
* statusCodes={
* 200="Analytics successfully returned.",
* 403="You don't have permissions to view this analytics.",
* 404="Can't find analytic by specified id."
* }
* )
*
* @param integer $id Analytic entity id.
*
* @return \CacheBundle\Entity\Analytic\Analytic|\ApiBundle\Response\ViewInterface
*/
public function getAction($id)
{
return parent::getEntity($id);
}
/**
* Delete specified analytic.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{id}",
* requirements={ "id"="\d+" },
* methods={ "DELETE" }
* )
* @AppApiDoc(
* resource=true,
* section="Analytic",
* statusCodes={
* 204="Analytic successfully deleted.",
* 403="You don't have permissions to delete this analytic.",
* 404="Can't find analytic by specified id."
* }
* )
*
* @param integer $id A Analytic entity id.
*
* @return array|\ApiBundle\Response\ViewInterface
*/
public function deleteAction($id)
{
$repository = $this->getManager()->getRepository($this->entity);
/** @var Analytic $analytic */
$analytic = $repository->find($id);
if (!$analytic instanceof Analytic) {
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
}
$reasons = $this->checkAccess(InspectorInterface::DELETE, $analytic);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$analyticContext = $analytic->getContext();
if (isset($analyticContext)) {
($analyticContext->getAnalytics()->count() == 1) ? $this->getManager()->remove($analyticContext) : "";
}
$this->getManager()->remove($analytic);
$this->getManager()->flush();
return $this->generateResponse();
}
/**
* Update analytic.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route("/{id}", methods={ "PUT" }, requirements={ "id"="\d+" })
* @AppApiDoc(
* section="Analytic",
* resource=true,
* input={
* "class"="CacheBundle\Form\AnalyticType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\Analytic\Analytic",
* "groups"={ "analytic", "id" }
* },
* statusCodes={
* 200="Analytics successfully updated.",
* 400="Invalid data provided."
* }
* )
*
* @param Request $request A Request instance.
* @param integer $id Analytic entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function putAction(Request $request, $id)
{
$user = $this->getCurrentUser();
$this->analyticFactory = $this->get('cache.analytic_factory');
/** @var AnalyticRepository $analyticRepository */
$repository = $this->getManager()->getRepository($this->entity);
/** @var Analytic $analytic */
$analytic = $repository->find($id);
if (!$analytic instanceof Analytic) {
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
}
$feeds = $analytic->getContext()->getFeeds();
$feedsId = [];
foreach ($feeds as $feedsVal) {
$feedsId[] = $feedsVal->getId();
}
$analyticDto = new AnalyticDTO($feedsId, null, $analytic->getContext()->getFilters(), $analytic->getContext()->getRawFilters());
$form = $this->createForm(AnalyticType::class, $analyticDto);
$form->submit($request->request->all());
if ($form->isValid()) {
/** @var AnalyticDTO $dto */
$dto = $form->getData();
try {
$analyticContext = $analytic->getContext();
if (isset($analyticContext)) {
($analyticContext->getAnalytics()->count() == 1) ? $this->getManager()->remove($analyticContext) : "";
}
$analytic = $this->analyticFactory->updateAnalytic($dto, $user, $analytic);
} catch (NotAllowedException $exception) {
return $this->generateResponse('You not allowed to update analytics');
}
$this->getManager()->persist($analytic);
$this->getManager()->flush();
return $this->generateResponse($analytic, 200, ['id', 'analytic']);
}
return $this->generateResponse($form, 400);
}
/**
* Get list of categories for current user.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(methods={ "GET" })
* @AppApiDoc(
* section="Analytic",
* output={
* "class"="Pagination<CacheBundle\Entity\Analytic\Analytic>",
* "groups"={ "analytic", "id","context" }
* },
* statusCodes={
* 200="List of analytic successfully returned."
* }
* )
*
* @param Request $request
* @return array|\ApiBundle\Response\ViewInterface
*/
public function listAction(Request $request)
{
/** @var AnalyticRepository $repository */
$repository = $this->getManager()->getRepository(Analytic::class);
$user = $this->getCurrentUser();
$pagination = $this->paginate(
$request,
$repository->getList($user->getId())
);
// Simulate pagination serialization.
return $this->generateResponse([
$pagination
], 200, [
'analytic',
'id',
'context'
]);
}
}
@@ -0,0 +1,250 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractCRUDController;
use ApiBundle\Controller\Annotation\Roles;
use CacheBundle\Entity\Analytic\Analytic;
use DateInterval;
use DatePeriod;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use CacheBundle\Repository\AnalyticRepository;
use CacheBundle\Entity\Document;
use Symfony\Component\HttpFoundation\Request;
/**
* Class AnalyticGraphController
* @package AppBundle\Controller\V1
*
* @Route(service="app.controller.analytic-graph")
*/
class AnalyticGraphController extends AbstractCRUDController
{
/**
* Get data for influence list
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/influencer/{id}",
* requirements={
* "id": "\d+",
* },
* methods={ "POST" }
* )
*
* @param Request $request
* @param $id
*
* @return array|\ApiBundle\Response\ViewInterface
*
*/
public function getInfluenceAction(Request $request, $id)
{
$isAuthorType = $request->request->get('isAuthorType', false);
$groupByField = 'source_hashcode';
if ($isAuthorType === true) {
$groupByField = 'author_name';
}
/** @var AnalyticRepository $analyticRepository */
$repository = $this->getManager()->getRepository($this->entity);
/** @var Analytic $analytic */
$analytic = $repository->find($id);
if (!$analytic instanceof Analytic) {
return $this->generateResponse("Can't find analytic with id {$id}.", 404);
}
$analyticContext = $analytic->getContext();
$feeds = $analyticContext->getFeeds();
$queryId = [];
$clipFeedId = [];
foreach ($feeds as $feedsVal) {
if ($feedsVal->getSubType() == 'query_feed') {
$queryId[] = $feedsVal->getQuery()->getId();
} else {
$clipFeedId[] = $feedsVal->getId();
}
}
$filters = $analyticContext->getFilters();
$influenceData = [];
if (count($filters) > 0) {
if (array_key_exists('date', $filters)) {
$startDt = $filters['date']->getFilters()[0]->getValue()->format('Y-m-d');
$endDt = $filters['date']->getFilters()[1]->getValue()->format('Y-m-d');
$repository = $this->getManager()->getRepository(Document::class);
$documents = $repository->getByQuery($queryId);
$clipDocuments = $repository->getByClip($clipFeedId);
foreach ($feeds as $key => $feedsVal) {
$influenceData[$key]['name'] = $feedsVal->getName();
$influenceData[$key]['data'] = [];
foreach ($documents as $document) {
if ($feedsVal->getSubType() == 'query_feed') {
if ($feedsVal->getQuery()->getId() == $document['id']) {
$publishDate = substr($document['data']['published'], 0, 10);
$publishDate = date('Y-m-d', strtotime($publishDate));
if (($publishDate >= $startDt) && ($publishDate <= $endDt)) {
if (array_key_exists($groupByField, $document['data'])) {
$engagementCount = 0;
if (array_key_exists("likes", $document['data'])) {
$engagementCount = $document['data']['likes'];
}
if (array_key_exists("dislikes", $document['data'])) {
$engagementCount += $document['data']['dislikes'];
}
if (array_key_exists("comments", $document['data'])) {
$engagementCount += $document['data']['comments'];
}
if (array_key_exists("shares", $document['data'])) {
$engagementCount += $document['data']['shares'];
}
$tempInfluenceData = $influenceData[$key]['data'];
if (count($tempInfluenceData) > 0) {
$sourceHashCodeKey = array_search($document['data'][$groupByField], array_column($tempInfluenceData, $groupByField));
if ($sourceHashCodeKey === false) {
$tempData = [$groupByField => $document['data'][$groupByField], 'influence' => $document['data']['source_link'],
'source_type' => $document['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
if (array_key_exists("sentiment", $document['data'])) {
$sentiment = 1;
$tempData['totalSentiment'] = $sentiment;
$tempData[$document['data']['sentiment']] = $sentiment;
}
array_push($tempInfluenceData, $tempData);
$influenceData[$key]['data'] = $tempInfluenceData;
} else {
if (array_key_exists("sentiment", $document['data'])) {
$influenceData[$key]['data'][$sourceHashCodeKey]['totalSentiment'] += 1;
if (array_key_exists($document['data']['sentiment'], $influenceData[$key]['data'][$sourceHashCodeKey])) {
$influenceData[$key]['data'][$sourceHashCodeKey][$document['data']['sentiment']] += 1;
} else {
$influenceData[$key]['data'][$sourceHashCodeKey][$document['data']['sentiment']] = 1;
}
}
$influenceData[$key]['data'][$sourceHashCodeKey]['engagement'] += $engagementCount;
}
} else {
$tempData = [$groupByField => $document['data'][$groupByField], 'influence' => $document['data']['source_link'],
'source_type' => $document['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
if (array_key_exists("sentiment", $document['data'])) {
$sentiment = 1;
$tempData['totalSentiment'] = $sentiment;
$tempData[$document['data']['sentiment']] = $sentiment;
}
$influenceData[$key]['data'][0] = $tempData;
}
}
}
}
}
}
foreach ($clipDocuments as $clipDocument) {
if ($feedsVal->getSubType() == 'clip_feed') {
if ($feedsVal->getId() == $clipDocument['clipFeedId']) {
$publishDate = substr($clipDocument['data']['published'], 0, 10);
$publishDate = date('Y-m-d', strtotime($publishDate));
if (($publishDate >= $startDt) && ($publishDate <= $endDt)) {
if (array_key_exists($groupByField, $clipDocument['data'])) {
$engagementCount = 0;
if (array_key_exists("likes", $clipDocument['data'])) {
$engagementCount = $clipDocument['data']['likes'];
}
if (array_key_exists("dislikes", $clipDocument['data'])) {
$engagementCount += $clipDocument['data']['dislikes'];
}
if (array_key_exists("comments", $clipDocument['data'])) {
$engagementCount += $clipDocument['data']['comments'];
}
if (array_key_exists("shares", $clipDocument['data'])) {
$engagementCount += $clipDocument['data']['shares'];
}
$tempInfluenceData = $influenceData[$key]['data'];
if (count($tempInfluenceData) > 0) {
$sourceHashCodeKey = array_search($clipDocument['data'][$groupByField], array_column($tempInfluenceData, $groupByField));
if ($sourceHashCodeKey === false) {
$tempData = [$groupByField => $clipDocument['data'][$groupByField], 'influence' => $clipDocument['data']['source_link'],
'source_type' => $clipDocument['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
if (array_key_exists("sentiment", $clipDocument['data'])) {
$sentiment = 1;
$tempData['totalSentiment'] = $sentiment;
$tempData[$clipDocument['data']['sentiment']] = $sentiment;
}
array_push($tempInfluenceData, $tempData);
$influenceData[$key]['data'] = $tempInfluenceData;
} else {
if (array_key_exists("sentiment", $clipDocument['data'])) {
$influenceData[$key]['data'][$sourceHashCodeKey]['totalSentiment'] += 1;
if (array_key_exists($clipDocument['data']['sentiment'], $influenceData[$key]['data'][$sourceHashCodeKey])) {
$influenceData[$key]['data'][$sourceHashCodeKey][$clipDocument['data']['sentiment']] += 1;
} else {
$influenceData[$key]['data'][$sourceHashCodeKey][$clipDocument['data']['sentiment']] = 1;
}
}
$influenceData[$key]['data'][$sourceHashCodeKey]['engagement'] += $engagementCount;
}
} else {
$tempData = [$groupByField => $clipDocument['data'][$groupByField], 'influence' => $clipDocument['data']['source_link'],
'source_type' => $clipDocument['data']['source_publisher_type'], 'engagement' => $engagementCount, 'totalSentiment' => 0];
if (array_key_exists("sentiment", $clipDocument['data'])) {
$sentiment = 1;
$tempData['totalSentiment'] = $sentiment;
$tempData[$clipDocument['data']['sentiment']] = $sentiment;
}
$influenceData[$key]['data'][0] = $tempData;
}
}
}
}
}
}
}
}
}
foreach ($influenceData as $key => $influenceDataVal) {
usort($influenceData[$key]['data'], function ($a, $b) {
return $b['totalSentiment'] <=> $a['totalSentiment'];
});
}
foreach ($influenceData as $key => $dataVal) {
$influenceData[$key]['data'] = array_slice($dataVal['data'], 0, 10);
}
return $this->generateResponse([
'data' => $influenceData
], 200, []);
}
/**
* @param $filters
*
* @return array
*
* @throws \Exception
*/
public function getDuration($filters)
{
$duration = [];
if (count($filters) > 0) {
if (array_key_exists('date', $filters)) {
$period = new DatePeriod(
$filters['date']->getFilters()[0]->getValue(),
new DateInterval('P1D'),
$filters['date']->getFilters()[1]->getValue()
);
foreach ($period as $key => $value) {
$duration[$value->format('Y-m-d')] = 0;
}
}
}
return $duration;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,329 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractCRUDController;
use ApiBundle\Controller\Annotation\Roles;
use ApiBundle\Response\ViewInterface;
use ApiBundle\Security\Inspector\InspectorInterface;
use ApiDocBundle\Controller\Annotation\AppApiDoc;
use CacheBundle\Entity\Category;
use CacheBundle\Repository\CategoryRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use UserBundle\Enum\AppLimitEnum;
/**
* Class CategoryController
* @package AppBundle\Controller\V1
*
* @Route("/categories", service="app.controller.category")
*/
class CategoryController extends AbstractCRUDController
{
/**
* Move specified feed to another category.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{movedId}/move_to/{destinationId}",
* requirements={
* "movedId": "\d+",
* "destinationId": "\d+"
* },
* methods={ "POST" }
* )
* @AppApiDoc(
* resource=true,
* section="Category",
* output={
* "class"="Pagination<CacheBundle\Entity\Category>",
* "groups"={ "id", "category_tree", "feed_tree" }
* },
* statusCodes={
* 200="List of updated categories successfully returned.",
* 400="Invalid data provided.",
* 403="You don't have permissions to move this category.",
* 404="Can't find moved or destination category."
* }
* )
*
* @param integer $movedId A moved Category entity id.
* @param integer $destinationId A Category entity id where the category is
* moved.
*
* @return \ApiBundle\Response\ViewInterface|\Symfony\Component\HttpFoundation\Response
*/
public function moveAction($movedId, $destinationId)
{
$movedId = (integer) $movedId;
$destinationId = (integer) $destinationId;
$userId = \app\op\invokeIf($this->getCurrentUser(), 'getId');
/** @var CategoryRepository $repository */
$repository = $this->getManager()->getRepository(Category::class);
$moved = $repository->get($movedId, $userId);
if (! $moved instanceof Category) {
return $this->generateResponse("Can't find category with id {$movedId}.", 404);
}
//
// Check that user don't try to move internal category.
//
if ($moved->isInternal()) {
return $this->generateResponse('Can\'t move internal category.', 403);
}
//
// We should don't make any changes if client try to move category into
// the same category.
//
if ($moved->getParent()->getId() !== $destinationId) {
$destination = $repository->get($destinationId, $userId, [
Category::TYPE_CUSTOM,
Category::TYPE_MY_CONTENT,
]);
if (! $destination instanceof Category) {
return $this->generateResponse("Can't find category with id {$destinationId}.", 404);
}
//
// All ok, now we need to validate destination category id.
//
$moved->setParent($destination);
/** @var ValidatorInterface $validator */
$validator = $this->get('validator');
$errors = $validator->validate($moved);
if (count($errors) > 0) {
//
// Get all violation errors and send it to client.
//
$errors = array_map(function (ConstraintViolationInterface $violation) {
return $violation->getMessage();
}, iterator_to_array($errors));
return $this->generateResponse($errors, 400);
}
//
// Validation passed, update entity.
//
$this->getManager()->persist($moved);
$this->getManager()->flush();
}
return $this->forward('app.controller.category:listAction');
}
/**
* Create new category for current user.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(methods={ "POST" })
* @AppApiDoc(
* resource=true,
* section="Category",
* input={
* "class"="CacheBundle\Form\CategoryType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\Category",
* "groups"={ "id", "category" }
* },
* statusCodes={
* 200="Category successfully created.",
* 400="Invalid data provided.",
* 403="You don't have permissions to create category."
* }
* )
*
* @param Request $request A Request instance.
*
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
*/
public function createAction(Request $request)
{
return parent::createEntity($request, new Category($this->getCurrentUser()));
}
/**
* Get list of categories for current user.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(methods={ "GET" })
* @AppApiDoc(
* section="Category",
* output={
* "class"="Pagination<CacheBundle\Entity\Category>",
* "groups"={ "id", "category_tree", "feed_tree" }
* },
* statusCodes={
* 200="List of categories successfully returned."
* }
* )
*
* @return ViewInterface
*/
public function listAction()
{
/** @var CategoryRepository $repository */
$repository = $this->getManager()->getRepository(Category::class);
$user = $this->getCurrentUser();
$categories = $repository->getList($user->getId());
$count = count($categories);
// Simulate pagination serialization.
return $this->generateResponse([
'data' => $categories,
'count' => $count,
'totalCount' => $count,
'page' => 1,
'limit' => $count,
], 200, [
'id',
'category_tree',
'feed_tree',
]);
}
/**
* Get specified category by id.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{id}",
* requirements={ "id"="\d+" },
* methods={ "GET" }
* )
* @AppApiDoc(
* resource=true,
* section="Category",
* output={
* "class"="CacheBundle\Entity\Category",
* "groups"={ "id", "category", "feed_tree" }
* },
* statusCodes={
* 200="Category successfully returned.",
* 403="You don't have permissions to view this category.",
* 404="Can't find category by specified id."
* }
* )
*
* @param integer $id A Category entity id.
*
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
*/
public function getAction($id)
{
return parent::getEntity($id);
}
/**
* Update specified category.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{id}",
* requirements={ "id"="\d+" },
* methods={ "PUT" }
* )
* @AppApiDoc(
* resource=true,
* section="Category",
* input={
* "class"="CacheBundle\Form\CategoryType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\Category",
* "groups"={ "id", "category" }
* },
* statusCodes={
* 200="Category successfully updated.",
* 400="Invalid data provided.",
* 403="You don't have permissions to update this category.",
* 404="Can't find category by specified id."
* }
* )
*
* @param Request $request A Request instance.
* @param integer $id A Category entity id.
*
* @return \CacheBundle\Entity\Category|\ApiBundle\Response\ViewInterface
*/
public function putAction(Request $request, $id)
{
return parent::putEntity($request, $id);
}
/**
* Delete specified category.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{id}",
* requirements={ "id"="\d+" },
* methods={ "DELETE" }
* )
* @AppApiDoc(
* resource=true,
* section="Category",
* statusCodes={
* 204="Category successfully deleted.",
* 403="You don't have permissions to delete this category.",
* 404="Can't find category by specified id."
* }
* )
*
* @param integer $id A Category entity id.
*
* @return array|\ApiBundle\Response\ViewInterface
*/
public function deleteAction($id)
{
/** @var CategoryRepository $repository */
$repository = $this->getManager()->getRepository($this->entity);
$category = $repository->find($id);
if ($category === null) {
return $this->generateResponse("Can't find category with id {$id}.", 404);
}
$reasons = $this->checkAccess(InspectorInterface::DELETE, $category);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
//
// Update restriction limit for current user only if deleted category has
// some feeds.
//
$feedCount = $repository->computeFeedCounts($id);
if ($feedCount > 0) {
$user = $this->getCurrentUser();
$user->releaseLimit(AppLimitEnum::feeds(), $feedCount);
$this->getManager()->persist($user);
}
$this->getManager()->remove($category);
$this->getManager()->flush();
return $this->generateResponse();
}
}
@@ -0,0 +1,111 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\Annotation\Roles;
use ApiBundle\Security\Inspector\InspectorInterface;
use ApiDocBundle\Controller\Annotation\AppApiDoc;
use CacheBundle\Entity\Comment;
use CacheBundle\Entity\Document;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* Class CommentController
* @package AppBundle\Controller\V1
*
* @Route("/comments", service="app.controller.comment")
*/
class CommentController extends AbstractV1CrudController
{
/**
* Update specified comment.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{commentId}",
* requirements={ "commentId"="\d+" },
* methods={ "PUT" }
* )
* @AppApiDoc(
* section="Comment",
* resource=false,input={
* "class"="CacheBundle\Form\CommentType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\Comment",
* "groups"={ "comment", "id" }
* },
* statusCodes={
* 200="Comment successfully updated.",
* 400="Invalid data provided.",
* 403="You don't have permissions to update this comment.",
* 404="Can't find comment by specified id."
* }
* )
*
* @param Request $request A Request instance.
* @param integer $commentId A one of comment entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
public function putAction(Request $request, $commentId)
{
return parent::putEntity($request, $commentId);
}
/**
* Delete specified comment.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{commentId}",
* requirements={ "commentId"="\d+" },
* methods={ "DELETE" }
* )
* @AppApiDoc(
* section="Comment",
* resource=false,
* statusCodes={
* 204="Comment successfully deleted.",
* 403="You don't have permissions to delete this comment.",
* 404="Can't find comment by specified id."
* }
* )
*
* @param integer $commentId A Comment entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function deleteAction($commentId)
{
$entity = $this->em->getRepository(Comment::class)->find($commentId);
if ($entity === null) {
return $this->generateResponse("Can't find comment with id {$commentId}.", 404);
}
$reasons = $this->checkAccess(InspectorInterface::DELETE, $entity);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$this->em->getRepository(Document::class)
->createQueryBuilder('Document')
->update()
->set('Document.commentsCount', 'Document.commentsCount - 1')
->where('Document.id = :id')
->setParameter('id', \app\op\invokeIf($entity->getDocument(), 'getId'))
->getQuery()
->execute();
$this->em->remove($entity);
$this->em->flush();
return $this->generateResponse();
}
}
@@ -0,0 +1,277 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\Annotation\Roles;
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
use ApiBundle\Security\Inspector\InspectorInterface;
use ApiDocBundle\Controller\Annotation\AppApiDoc;
use AppBundle\Controller\Traits\AccessCheckerTrait;
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
use AppBundle\Entity\EmailedDocument;
use AppBundle\Form\EmailedDocumentType;
use CacheBundle\Comment\Manager\CommentManagerInterface;
use CacheBundle\Entity\Comment;
use CacheBundle\Entity\Document;
use CacheBundle\Repository\CommentRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Pagination\Paginator;
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Class DocumentController
* @package AppBundle\Controller\V1
*
* @Route("/documents", service="app.controller.document")
*/
class DocumentController extends AbstractV1Controller
{
use
TokenStorageAwareTrait,
FormFactoryAwareTrait,
AccessCheckerTrait;
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var CommentManagerInterface
*/
private $commentManager;
/**
* @var ProducerInterface
*/
private $emailProducer;
/**
* DocumentController constructor.
*
* @param TokenStorageInterface $tokenStorage A TokenStorageInterface
* instance.
* @param FormFactoryInterface $formFactory A FormFactoryInterface
* instance.
* @param AccessCheckerInterface $accessChecker A AccessCheckerInterface
* instance.
* @param EntityManagerInterface $em A EntityManagerInterface
* instance.
* @param CommentManagerInterface $commentManager A CommentManagerInterface
* instance.
* @param ProducerInterface $emailProducer A producer interface for
* emailing documents.
*/
public function __construct(
TokenStorageInterface $tokenStorage,
FormFactoryInterface $formFactory,
AccessCheckerInterface $accessChecker,
EntityManagerInterface $em,
CommentManagerInterface $commentManager,
ProducerInterface $emailProducer
) {
$this->tokenStorage = $tokenStorage;
$this->formFactory = $formFactory;
$this->accessChecker = $accessChecker;
$this->em = $em;
$this->commentManager = $commentManager;
$this->emailProducer = $emailProducer;
}
/**
* Create new comment for specified document.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{documentId}/comments",
* requirements={ "documentId"="\d+" },
* methods={ "POST" }
* )
* @AppApiDoc(
* section="Document",
* resource=true,
* input={
* "class"="CacheBundle\Form\CommentType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\Comment",
* "groups"={ "comment", "id" }
* },
* statusCodes={
* 200="Comment successfully saved.",
* 400="Invalid data provided."
* }
* )
*
* @param Request $request A Request instance.
* @param integer $documentId Commented Document entity id.
*
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
*/
public function createCommentAction(Request $request, $documentId)
{
$document = $this->em->getRepository(Document::class)->find($documentId);
if (! $document instanceof Document) {
return $this->generateResponse([[
'message' => 'Document not found',
'transKey' => 'commentDocumentInvalidDocument',
'type' => 'error',
'parameters' => [ 'current' => $documentId ],
], ], 404);
}
$comment = new Comment($this->getCurrentUser(), '');
$form = $this->createForm($comment->getCreateFormClass(), $comment);
// Submit data into form.
$form->submit($request->request->all());
if ($form->isValid()) {
//
// Check that current user can create this entity.
// If user don't have rights to create this entity we should send all
// founded restrictions to client.
//
$reasons = $this->checkAccess(InspectorInterface::CREATE, $comment);
if (count($reasons) > 0) {
//
// User don't have rights to create this entity so send all
// founded restriction reasons to client.
//
return $this->generateResponse($reasons, 403);
}
$this->commentManager->addComment($comment, $document);
$this->em->persist($comment);
$this->em->flush();
return $comment;
}
// Client send invalid data.
return $this->generateResponse($form, 400);
}
/**
* Get list of comments for specified document.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/{documentId}/comments",
* requirements={ "documentId"="\d+" },
* methods={ "GET" }
* )
* @AppApiDoc(
* section="Document",
* resource=true,
* filters={
* {
* "name"="offset",
* "dataType"="integer",
* "description"="Offset from beginning of collection, start from 1",
* "requirements"="\d+",
* "default"="1"
* },
* {
* "name"="limit",
* "dataType"="integer",
* "description"="Max entities per page, default 10",
* "requirements"="\d+",
* "default"="10"
* },
* },
* output={
* "class"="Paginated<CacheBundle\Entity\Comment>",
* "groups"={ "comment", "id" }
* },
* statusCodes={
* 200="List of comments returned.",
* 404="Invalid document id."
* }
* )
* @param Request $request A Request instance.
* @param integer $documentId A Document entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function getCommentsAction(Request $request, $documentId)
{
$document = $this->em->getRepository(Document::class)->find($documentId);
if (! $document instanceof Document) {
return $this->generateResponse([[
'message' => 'Document not found',
'transKey' => 'getDocumentCommentsInvalidDocument',
'type' => 'error',
'parameters' => [ 'current' => $documentId ],
], ], 404);
}
/** @var CommentRepository $repository */
$repository = $this->em->getRepository(Comment::class);
$qb = $repository->getListForDocument($documentId);
$offset = $request->query->getInt('offset', CommentManagerInterface::NEW_COMMENT_POOL_SIZE);
$limit = $request->query->getInt('limit', 10);
$qb
->setFirstResult($offset)
->setMaxResults($limit);
return $this->generateResponse(new Paginator($qb), 200, [ 'id', 'comment' ]);
}
/**
* Send specified documents content to recipients.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route(
* "/email",
* methods={ "POST" }
* )
* @AppApiDoc(
* section="Document",
* resource=true,
* input={
* "class"="AppBundle\Form\EmailedDocumentType",
* "name"=false
* },
* statusCodes={
* 204="Email's sent.",
* 400="Invalid data."
* }
* )
* @param Request $request A Request instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function emailAction(Request $request)
{
$emailedDocument = new EmailedDocument();
$form = $this->createForm(EmailedDocumentType::class, $emailedDocument);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$this->em->persist($emailedDocument);
$this->em->flush();
$this->emailProducer->publish($emailedDocument->getId());
return $this->generateResponse();
}
return $this->generateResponse($form, 400);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,269 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\Annotation\Roles;
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
use AppBundle\Exception\LimitExceedException;
use AppBundle\Form\SearchRequest\SimpleQuerySearchRequestType;
use AppBundle\Manager\SimpleQuery\SimpleQueryManagerInterface;
use AppBundle\Manager\Source\SourceManagerInterface;
use CacheBundle\Document\Extractor\DocumentContentExtractorInterface;
use Common\Enum\FieldNameEnum;
use Doctrine\ORM\EntityManagerInterface;
use IndexBundle\Model\ArticleDocumentInterface;
use IndexBundle\SearchRequest\SearchRequestBuilderInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use UserBundle\Enum\AppLimitEnum;
use UserBundle\Enum\ThemeOptionExtractEnum;
/**
* Class QueryController
* @package AppBundle\Controller\V1
*
* @Route("/query", service="app.controller.query")
*/
class QueryController extends AbstractV1Controller
{
use
FormFactoryAwareTrait,
TokenStorageAwareTrait;
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var SourceManagerInterface
*/
private $sourceManager;
/**
* @var SimpleQueryManagerInterface
*/
private $queryManager;
/**
* @var DocumentContentExtractorInterface
*/
private $extractor;
/**
* QueryController constructor.
*
* @param FormFactoryInterface $formFactory A
* FormFactoryInterface
* instance.
* @param EntityManagerInterface $em A
* RestrictionsRepositoryInterface
* instance.
* @param TokenStorageInterface $tokenStorage A
* TokenStorageInterface
* instance.
* @param SourceManagerInterface $sourceManager A
* SourceManagerInterface
* instance.
* @param SimpleQueryManagerInterface $queryManager A
* SimpleQueryManagerInterface
* instance.
* @param DocumentContentExtractorInterface $extractor A
* DocumentContentExtractorInterface
* instance.
*/
public function __construct(
FormFactoryInterface $formFactory,
EntityManagerInterface $em,
TokenStorageInterface $tokenStorage,
SourceManagerInterface $sourceManager,
SimpleQueryManagerInterface $queryManager,
DocumentContentExtractorInterface $extractor
) {
$this->formFactory = $formFactory;
$this->em = $em;
$this->tokenStorage = $tokenStorage;
$this->sourceManager = $sourceManager;
$this->queryManager = $queryManager;
$this->extractor = $extractor;
}
/**
* Make simple search without saving query in database.
* Fetched documents are cached but not indexed.
*
* @Roles("ROLE_SUBSCRIBER")
*
* @Route("/search", methods={ "POST" })
* @ApiDoc(
* resource="Search",
* section="Query",
* input={
* "class"="AppBundle\Form\SearchRequest\SimpleQuerySearchRequestType",
* "name"=false
* },
* output={
* "class"="",
* "data"={
* "documents"={
* "class"="Pagination<CacheBundle\Entity\Document>",
* "groups"={ "document" }
* },
* "advancedFilters"={
* "dataType"="array",
* "required"=true,
* "readonly"=true,
* "description"="Array of advanced filters values for this search request."
* },
* "stats"={
* "dataType"="object",
* "required"=true,
* "readonly"=true,
* "description"="Internal statistics, showed only in staging and local developers machine.",
* "children"={
* "totalOnPage"={
* "dataType"="integer",
* "required"=true,
* "readonly"=true,
* "description"="Total founded document on current page."
* },
* "newDocuments"={
* "dataType"="integer",
* "required"=true,
* "readonly"=true,
* "description"="Number of documents that were not in our database."
* },
* "alreadyExistsDocuments"={
* "dataType"="integer",
* "required"=true,
* "readonly"=true,
* "description"="Number of documents that already in our database."
* },
* "fromCache"={
* "dataType"="boolean",
* "required"=true,
* "readonly"=true,
* "description"="Flag, all documents fetched from our internal cache if set."
* },
* "expiresAt"={
* "dataType"="datetime",
* "required"=true,
* "readonly"=true,
* "description"="When this query is expired."
* }
* }
* }
* }
* },
* statusCodes={
* 200="Search completed.",
* 400="Invalid data provided"
* }
* )
*
* @param Request $request A Request instance.
*
* @return array|\ApiBundle\Response\ViewInterface
*/
public function searchAction(Request $request)
{
$form = $this->createForm(SimpleQuerySearchRequestType::class);
$form->submit($request->request->all());
if ($form->isValid()) {
$user = $this->getCurrentUser();
try {
$user->useLimit(AppLimitEnum::searches());
} catch (LimitExceedException $exception) {
return $this->generateResponse([
'failedRestriction' => AppLimitEnum::SEARCHES,
'restrictions' => $user->getRestrictions(),
], 402);
}
$this->em->persist($user);
$this->em->flush();
/** @var SearchRequestBuilderInterface $builder */
$builder = $form->getData();
$searchRequest = $builder
->setFields([
FieldNameEnum::TITLE,
FieldNameEnum::MAIN,
])
->addSort(FieldNameEnum::PUBLISHED, 'desc')
->build();
$response = $this->queryManager->searchAndCache(
$searchRequest,
$request->request->get('filters', []),
$request->request->get('advancedFilters', [])
);
$query = $response->getQuery();
$response->mapDocuments(function (ArticleDocumentInterface $document) use ($query) {
return $document->mapNormalizedData(function (array $data) use ($query) {
$result = $this->extractor->extract(
$data['content'],
$query->getRaw(),
ThemeOptionExtractEnum::start(),
true
);
$data['content'] = $result->getText() . (
mb_strlen($data['content']) > $result->getLength()
? '...'
: ''
);
return $data;
});
});
$result = [
'documents' => $this->paginate($response, $builder->getPage(), $builder->getLimit()),
'advancedFilters' => $searchRequest->getAvailableAdvancedFilters() ?: (object) [],
];
//
// Return internal statistic.
//
$result['stats'] = [
'newDocuments' => $response->getUniqueCount(),
'alreadyExistsDocuments' => $response->count() - $response->getUniqueCount(),
'fromCache' => $response->isFromCache(),
'expiresAt' => $query->getExpirationDate()->format('c'),
];
//
// Return meta information about query.
//
$sources = $this->sourceManager->getSourcesForQuery($query, [ 'id', 'title', 'type' ]);
$sourceLists = $this->sourceManager->getSourceListsForQuery($query, [ 'id', 'name' ]);
$result['meta'] = [
'type' => 'query',
'status' => 'synced',
'search' => [
'query' => $query->getRaw(),
'filters' => $query->getRawFilters() ?: (object) [],
'advancedFilters' => $query->getRawAdvancedFilters() ?: (object) [],
],
'sources' => $sources,
'sourceLists' => $sourceLists,
];
return $this->generateResponse($result);
}
return $this->generateResponse($form, 400);
}
}
@@ -0,0 +1,313 @@
<?php
namespace AppBundle\Controller\V1;
use AppBundle\Controller\Traits\FormFactoryAwareTrait;
use AppBundle\Controller\Traits\TokenStorageAwareTrait;
use AppBundle\Manager\Source\SourceManagerInterface;
use CacheBundle\Entity\SourceList;
use CacheBundle\Form\Sources\SourceSearchType;
use CacheBundle\Repository\SourceListRepository;
use Doctrine\ORM\EntityManagerInterface;
use IndexBundle\Model\SourceDocument;
use IndexBundle\SearchRequest\SearchRequestBuilder;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use ApiBundle\Controller\Annotation\Roles;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Class SourceIndexController
* @package AppBundle\Controller\V1
*
* @Route("/source-index", service="app.controller.source-index")
*/
class SourceIndexController extends AbstractV1Controller
{
use
TokenStorageAwareTrait,
FormFactoryAwareTrait;
/**
* @var SourceManagerInterface
*/
private $sourceManager;
/**
* @var EntityManagerInterface
*/
private $em;
/**
* SourceIndexController constructor.
*
* @param TokenStorageInterface $tokenStorage A TokenStorageInterface
* instance.
* @param FormFactoryInterface $formFactory A FormFactoryInterface
* instance.
* @param SourceManagerInterface $sourceManager A SourceManagerInterface
* instance.
* @param EntityManagerInterface $em A EntityManagerInterface
* instance.
*/
public function __construct(
TokenStorageInterface $tokenStorage,
FormFactoryInterface $formFactory,
SourceManagerInterface $sourceManager,
EntityManagerInterface $em
) {
$this->tokenStorage = $tokenStorage;
$this->formFactory = $formFactory;
$this->sourceManager = $sourceManager;
$this->em = $em;
}
/**
* Fetch all sources from our cache.
*
* @Route("/", methods={ "POST" })
*
* @ApiDoc(
* resource=true,
* section="Source Index",
* input={
* "class"="CacheBundle\Form\Sources\SourceSearchType",
* "name"=false
* },
* output={
* "class"="Pagination<IndexBundle\Model\SourceDocument>",
* "groups"={ "id", "source" }
* }
* )
*
* @param Request $request A Request instance.
*
* @return \Knp\Component\Pager\Pagination\PaginationInterface|\ApiBundle\Response\ViewInterface
*/
public function listAction(Request $request)
{
$form = $this->createForm(SourceSearchType::class);
$form->submit($request->request->all());
if ($form->isValid()) {
/** @var SearchRequestBuilder $searchRequestBuilder */
$searchRequestBuilder = $form->getData();
$searchRequestBuilder->setUser($this->getCurrentUser());
$response = $this->sourceManager->find($searchRequestBuilder);
$advancedFilters = $this->sourceManager->getAvailableFilters($searchRequestBuilder);
$sort = $searchRequestBuilder->getSorts();
$sort = [
'field' => array_search(key($sort), SourceSearchType::$fields),
'direction' => current($sort),
];
return $this->generateResponse([
'sources' => $this->paginate($response, $searchRequestBuilder->getPage(), $searchRequestBuilder->getLimit()),
'advancedFilters' => $advancedFilters ?: (object) [],
'meta' => [
'query' => $request->request->get('query'),
'advancedFilters' => $request->request->get('advancedFilters', [])?: (object) [],
'sort' => $sort,
],
], 200, [ 'id', 'source' ]);
}
return $this->generateResponse($form, 400);
}
/**
* Replace source lists for specified source.
*
* @Route("/{id}/list", methods={ "POST" })
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source Index",
* parameters={
* "sourceList"={
* "name"="sourceLists",
* "dataType"="array",
* "description"="Array of source lists ids."
* }
* }
* )
*
* @param Request $request A Request instance.
* @param integer $id A Source entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function replaceListAction(Request $request, $id)
{
if (count($this->sourceManager->getIndex()->has($id)) > 0) {
return $this->generateResponse([ [
'message' => "Can't find source with id {$id}",
'transKey' => 'replaceSourceUnknown',
'type' => 'error',
'parameters' => [ 'current' => $id ],
], ], 404);
}
$user = $this->getCurrentUser();
$sourceLists = $request->request->get('sourceLists');
if (! is_array($sourceLists)) {
return $this->generateResponse([[
'message' => 'sourceLists: This value should not be empty.',
'transKey' => 'replaceSourceListsEmpty',
'type' => 'error',
'parameters' => [ 'current' => null ],
], ], 400);
}
/** @var SourceListRepository $repository */
$repository = $this->em->getRepository(SourceList::class);
$foundedIds = $repository->sanitizeIds($sourceLists, $user->getId());
if (count($foundedIds) !== count($sourceLists)) {
//
// Some of provided id is not found or not owned by current user.
//
return $this->generateResponse([ [
'message' => 'sourceLists: This value is invalid.',
'transKey' => 'replaceSourceListInvalid',
'type' => 'error',
'parameters' => [
'current' => $sourceLists,
'invalid' => array_diff($sourceLists, $foundedIds),
],
], ], 400);
}
$this->sourceManager->replaceRelation($id, $foundedIds);
return $this->generateResponse();
}
/**
* Add Sources to Sources lists
*
* @Route("/add-to-sources-list", methods={ "POST" })
*
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source Index",
* parameters={
* "sources"={
* "name"="sources",
* "dataType"="integer",
* "actualType"="collection",
* "required"=true,
* "description"="Array of Source id."
* },
* "sourceLists"={
* "name"="sourceLists",
* "dataType"="integer",
* "actualType"="collection",
* "required"=true,
* "description"="Array of Sources Lists id."
* },
* }
* )
*
* @param Request $request A Request instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function addToSourceListAction(Request $request)
{
$sources = (array) $request->request->get('sources', []);
$sourceLists = (array) $request->request->get('sourceLists', []);
//
// Check that all fields are provided.
//
if (count($sources) === 0) {
return $this->generateResponse(
[
[
'message' => 'Sources should be selected.',
'transKey' => 'sourceToListsSourcesEmpty',
'type' => 'error',
'parameters' => [
'current' => $sources,
],
],
],
400
);
}
if (count($sourceLists) === 0) {
return $this->generateResponse(
[
[
'message' => 'Source lists should be selected.',
'transKey' => 'sourceToListsSourceListsEmpty',
'type' => 'error',
'parameters' => [
'current' => $sourceLists,
],
],
],
400
);
}
$user = $this->getCurrentUser();
//
// Validate specified sources and source lists ids.
//
/** @var SourceListRepository $repository */
$repository = $this->em->getRepository('CacheBundle:SourceList');
$existsSources = $this->sourceManager->getIndex()->get($sources, 'id');
$existsSources = array_map(function (SourceDocument $document) {
return $document['id'];
}, $existsSources);
if (count($sources) !== count($existsSources)) {
return $this->generateResponse(
[
[
'message' => 'sources: This value is invalid.',
'transKey' => 'sourceToListsSourcesInvalid',
'type' => 'error',
'parameters' => $sources,
],
],
400
);
}
$existsSourceLists = $repository->sanitizeIds($sourceLists, $user->getId());
if (count($sourceLists) !== count($existsSourceLists)) {
return $this->generateResponse(
[
[
'message' => 'sourceLists: This value is invalid.',
'transKey' => 'sourceToListsSourceListsInvalid',
'type' => 'error',
'parameters' => $sourceLists,
],
],
400
);
}
$this->sourceManager->bindSourcesToLists($user, $sources, $sourceLists);
return $this->generateResponse();
}
}
@@ -0,0 +1,486 @@
<?php
namespace AppBundle\Controller\V1;
use ApiBundle\Controller\AbstractApiController;
use ApiBundle\Security\Inspector\InspectorInterface;
use AppBundle\AppBundleServices;
use AppBundle\Manager\Source\SourceManagerInterface;
use CacheBundle\CacheBundleServices;
use CacheBundle\Entity\SourceList;
use CacheBundle\Entity\SourceToSourceList;
use CacheBundle\Form\Sources\SourceListSearchType;
use CacheBundle\Form\Sources\SourceListType;
use CacheBundle\Form\Sources\SourceSearchType;
use CacheBundle\Repository\SourceListRepository;
use CacheBundle\Security\Inspector\SourceListInspector;
use IndexBundle\SearchRequest\SearchRequestBuilder;
use Knp\Component\Pager\PaginatorInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use ApiBundle\Controller\Annotation\Roles;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Class SourceIndexController
* @package AppBundle\Controller\V1
*
* @Route("/source-list", service="app.controller.source-list")
*/
class SourceListController extends AbstractApiController
{
/**
* Get list of sources for the user
*
* @Route("/list", methods={ "POST" })
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source List",
* input={
* "class"="CacheBundle\Form\Sources\SourceListSearchType",
* "name"=false
* },
* output={
* "class"="Pagination<CacheBundle\Entity\SourceList>",
* "groups"={ "id", "source_list" }
* }
* )
*
* @param Request $request A Request instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function indexAction(Request $request)
{
$form = $this->createForm(SourceListSearchType::class);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$page = (int) $data['page'];
$limit = (int) $data['limit'];
$onlyShared = (boolean) $data['onlyShared'];
$user = $this->getCurrentUser();
$em = $this->getManager();
/** @var SourceListRepository $sourceListRepository */
$sourceListRepository = $em->getRepository(SourceList::class);
/** @var PaginatorInterface $paginator */
$paginator = $this->get('knp_paginator');
$qb = $sourceListRepository->getSourcesListsQB($user->getId(), $data['sort'], $onlyShared);
$pagination = $paginator->paginate(
$qb,
$page,
$limit
);
$sort = $data['sort'] ;
$sort = [
'field' => array_search(key($sort), SourceListSearchType::$fields),
'direction' => current($sort),
];
/** @var NormalizerInterface $normalizer */
$normalizer = $this->get('serializer');
$result = $normalizer->normalize($pagination, null, ['id', 'source_list']);
$result['sort'] = $sort;
return $this->generateResponse($result, 200);
}
return $this->generateResponse($form, 400);
}
/**
* Create a source list
*
* @Route("/", methods={ "POST" })
*
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source List",
* input={
* "class"="CacheBundle\Form\Sources\SourceListType",
* "name"=false
* },
* output={
* "class"="CacheBundle\Entity\SourceList",
* }
* )
*
* @param Request $request A Request entity instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function createAction(Request $request)
{
$sourceList = new SourceList();
$sourceList->setUser($this->getCurrentUser());
$form = $this->createForm(SourceListType::class, $sourceList);
$form->submit($request->request->all());
if ($form->isValid()) {
$em = $this->getManager();
$em->persist($sourceList);
$em->flush();
return $this->generateResponse($sourceList, 200, [
'source_list',
'id',
]);
}
return $this->generateResponse($form, 400);
}
/**
* Rename a source list
*
* @Route(
* "/{id}",
* requirements={ "id": "\d+" },
* methods={ "PUT" }
* )
*
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source List",
* parameters={
* "name"={
* "name"="name",
* "dataType"="string",
* "required"="true",
* "description"="A new name of the source list"
* }
* }
* )
*
* @param Request $request A HTTP Request instance.
* @param SourceList $sourceList A updated SourceList instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function updateAction(Request $request, SourceList $sourceList)
{
$form = $this->createForm(SourceListType::class, $sourceList);
$form->submit($request->request->all());
if ($form->isValid()) {
$em = $this->getManager();
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $sourceList);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
$sourceList->setUpdatedBy($this->getCurrentUser());
$em->persist($sourceList);
$em->flush();
return $this->generateResponse($sourceList, 200, [
'source_list',
'id',
]);
}
return $this->generateResponse($form, 400);
}
/**
* Delete a source list
*
* @Route("/{id}",
* requirements={ "id": "\d+" },
* methods={ "DELETE" }
* )
*
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource=true,
* section="Source List",
* parameters={
* "id"={
* "name"="id",
* "dataType"="integer",
* "required"="true",
* "description"="Id of the source list which changing"
* }
* }
* )
*
* @param SourceList $sourceList A deleted SourceList instance.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function deleteAction(SourceList $sourceList)
{
$reasons = $this->checkAccess(InspectorInterface::DELETE, $sourceList);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
//
// Remove this source list from all sources which is contains in it.
//
/** @var SourceManagerInterface $sourceManager */
$sourceManager = $this->container->get(CacheBundleServices::SOURCE_CACHE);
$sourceManager->unbindSourcesFromLists($sourceList->getId());
$em = $this->getManager();
$em->remove($sourceList);
$em->flush();
return $this->generateResponse();
}
/**
* Get list of sources for specified source list.
*
* @Route("/{id}/sources/search",
* requirements={ "id": "\d+" },
* methods={ "POST" }
* )
*
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource="Sources of specified source list",
* section="Source List",
* input={
* "class"="CacheBundle\Form\Sources\SourceSearchType",
* "name"=false
* }
* )
*
* @param Request $request A Request instance.
* @param integer $id A SourceList entity id.
*
* @return \Knp\Component\Pager\Pagination\PaginationInterface|\ApiBundle\Response\ViewInterface
*/
public function sourcesAction(Request $request, $id)
{
$user = $this->getCurrentUser();
/** @var SourceListRepository $repository */
$repository = $this->getManager()->getRepository('CacheBundle:SourceList');
$sourceList = $repository->getSourcesLists($id, $user->getId());
if ($sourceList === null) {
return $this->generateResponse("Can't find source list with id $id", 404);
}
$form = $this->createForm(SourceSearchType::class);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
/** @var SearchRequestBuilder $searchRequestBuilder */
$searchRequestBuilder = $form->getData();
/** @var SourceManagerInterface $manager */
$manager = $this->get(AppBundleServices::SOURCE_MANAGER);
$searchRequestBuilder->setUser($user);
$response = $manager->find($searchRequestBuilder, $sourceList);
/** @var PaginatorInterface $paginator */
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$response,
$searchRequestBuilder->getPage(),
$searchRequestBuilder->getLimit()
);
$sort = $searchRequestBuilder->getSorts();
$sort = [
'field' => array_search(key($sort), SourceSearchType::$fields),
'direction' => current($sort),
];
return $this->generateResponse([
'sources' => $pagination,
'filters' => $request->request->get('filters', (object) []),
'sort' => $sort,
], 200, [ 'id', 'source' ]);
}
return $this->generateResponse($form, 400);
}
/**
* Clone current list.
*
* @Route("/{id}/clone",
* requirements={ "id": "\d+" },
* methods={ "POST" }
* )
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource="Clone specified source list",
* section="Source List",
* parameters={
* {
* "name"="name",
* "dataType"="string",
* "required"="true"
* }
* },
* output={
* "class"="CacheBundle\Entity\SourceList",
* "groups"={ "id", "source_list" }
* }
* )
*
* @param Request $request A Request instance.
* @param integer $id A SourceList entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function cloneAction(Request $request, $id)
{
$user = $this->getCurrentUser();
/** @var SourceListRepository $repository */
$repository = $this->getManager()->getRepository('CacheBundle:SourceList');
$sourceList = $repository->getSourcesLists($id, $user->getId());
$em = $this->getManager();
$name = $request->request->get('name');
if ($name === null) {
return $this->generateResponse('Required field \'name\' is not provided or empty.');
}
if ($sourceList === null) {
return $this->generateResponse("Can't find source list with id $id", 404);
}
$clone = $sourceList->cloneList();
$clone->setName($name);
/** @var SourceManagerInterface $sourceManager */
$sourceManager = $this->get(CacheBundleServices::SOURCE_CACHE);
$sources = $sourceList->getSources()->map(function (SourceToSourceList $source) {
return $source->getSource();
})->toArray();
$em->persist($clone);
$em->flush();
//
// We should add and original id 'cause otherwise he lost his binding.
//
$sourceManager->bindSourcesToLists($user, $sources, [ $sourceList->getId(), $clone->getId() ]);
return $this->generateResponse($clone, 200, [
'source_list',
'id',
]);
}
/**
* Share specified source list.
*
* @Route("/{id}/share",
* requirements={ "id": "\d+" },
* methods={ "POST" }
* )
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource="Sharing",
* section="Source List"
* )
*
* @param string $id A SourceList entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function shareAction($id)
{
$user = $this->getCurrentUser();
$em = $this->getManager();
/** @var SourceListRepository $repository */
$repository = $em->getRepository('CacheBundle:SourceList');
$sourceList = $repository->getSourcesLists($id, $user->getId());
if ($sourceList === null) {
return $this->generateResponse("Can't find source list with id $id", 404);
}
$reasons = $this->checkAccess(SourceListInspector::SHARE, $sourceList);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
if (! $sourceList->getIsGlobal()) {
$sourceList
->setUpdatedBy($this->getCurrentUser())
->setIsGlobal(true);
$em->persist($sourceList);
$em->flush();
}
return $this->generateResponse();
}
/**
* Unshare specified source list.
*
* @Route("/{id}/unshare",
* requirements={ "id": "\d+" },
* methods={ "POST" }
* )
* @Roles("ROLE_SUBSCRIBER")
*
* @ApiDoc(
* resource="Sharing",
* section="Source List"
* )
*
* @param string $id A SourceList entity id.
*
* @return \ApiBundle\Response\ViewInterface
*/
public function unshareAction($id)
{
$user = $this->getCurrentUser();
$em = $this->getManager();
/** @var SourceListRepository $repository */
$repository = $em->getRepository('CacheBundle:SourceList');
$sourceList = $repository->getSourcesLists($id, $user->getId());
if ($sourceList === null) {
return $this->generateResponse("Can't find source list with id $id", 404);
}
$reasons = $this->checkAccess(SourceListInspector::UNSHARE, $sourceList);
if (count($reasons) > 0) {
return $this->generateResponse($reasons, 403);
}
if ($sourceList->getIsGlobal()) {
$sourceList
->setUpdatedBy($this->getCurrentUser())
->setIsGlobal(false);
$em->persist($sourceList);
$em->flush();
}
return $this->generateResponse();
}
}