Comment work with annotator v2

- add missing annotator.js file and fix typo
- edit & delete routes, started tests
- basic tests
This commit is contained in:
Thomas Citharel 2016-02-07 16:52:59 +01:00 committed by Jeremy Benoist
parent d3f1a9dc1a
commit f38e03dc02
20 changed files with 828 additions and 9 deletions

View file

@ -35,6 +35,7 @@ class AppKernel extends Kernel
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
new Craue\ConfigBundle\CraueConfigBundle(), new Craue\ConfigBundle\CraueConfigBundle(),
new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(), new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(),
new Wallabag\CommentBundle\WallabagCommentBundle(),
]; ];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {

View file

@ -1,3 +1,7 @@
wallabag_comment:
type : rest
resource: "@WallabagCommentBundle/Resources/config/routing_comments.yml"
wallabag_import: wallabag_import:
resource: "@WallabagImportBundle/Controller/" resource: "@WallabagImportBundle/Controller/"
type: annotation type: annotation

View file

@ -1,3 +1,4 @@
Rest_Wallabag: Rest_Wallabag:
type : rest type : rest
resource: "@WallabagApiBundle/Resources/config/routing_rest.yml" resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"

View file

@ -58,4 +58,5 @@ security:
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/settings, roles: ROLE_SUPER_ADMIN }
- { path: ^/annotations, roles: ROLE_USER }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -0,0 +1,146 @@
<?php
namespace Wallabag\CommentBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Wallabag\CommentBundle\Entity\Comment;
use Wallabag\CoreBundle\Entity\Entry;
class WallabagCommentController extends FOSRestController
{
/**
* Retrieve comments for an entry.
*
* @ApiDoc(
* requirements={
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
* }
* )
*
* @return Response
*/
public function getAnnotationsAction(Entry $entry)
{
$commentRows = $this
->getDoctrine()
->getRepository('WallabagCommentBundle:Comment')
->findCommentsByPageId($entry->getId(), $this->getUser()->getId());
$total = count($commentRows);
$comments = array('total' => $total, 'rows' => $commentRows);
$json = $this->get('serializer')->serialize($comments, 'json');
return $this->renderJsonResponse($json);
}
/**
* Creates a new comment.
*
* @param Entry $entry
*
* @ApiDoc(
* requirements={
* {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
* {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the comment"},
* {"name"="text", "dataType"="string", "required"=true, "description"=""},
* }
* )
*
* @return Response
*/
public function postAnnotationAction(Request $request, Entry $entry)
{
$data = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
$comment = new Comment($this->getUser());
$comment->setText($data['text']);
if (array_key_exists('quote', $data)) {
$comment->setQuote($data['quote']);
}
if (array_key_exists('ranges', $data)) {
$comment->setRanges($data['ranges']);
}
$comment->setEntry($entry);
$em->persist($comment);
$em->flush();
$json = $this->get('serializer')->serialize($comment, 'json');
return $this->renderJsonResponse($json);
}
/**
* Updates a comment.
*
* @ApiDoc(
* requirements={
* {"name"="comment", "dataType"="string", "requirement"="\w+", "description"="The comment ID"}
* }
* )
*
* @ParamConverter("comment", class="WallabagCommentBundle:Comment")
*
* @return Response
*/
public function putAnnotationAction(Comment $comment, Request $request)
{
$data = json_decode($request->getContent(), true);
if (!is_null($data['text'])) {
$comment->setText($data['text']);
}
$em = $this->getDoctrine()->getManager();
$em->flush();
$json = $this->get('serializer')->serialize($comment, 'json');
return $this->renderJsonResponse($json);
}
/**
* Removes a comment.
*
* @ApiDoc(
* requirements={
* {"name"="comment", "dataType"="string", "requirement"="\w+", "description"="The comment ID"}
* }
* )
*
* @ParamConverter("comment", class="WallabagCommentBundle:Comment")
*
* @return Response
*/
public function deleteAnnotationAction(Comment $comment)
{
$em = $this->getDoctrine()->getManager();
$em->remove($comment);
$em->flush();
$json = $this->get('serializer')->serialize($comment, 'json');
return $this->renderJsonResponse($json);
}
/**
* Send a JSON Response.
* We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
*
* @param string $json
*
* @return Response
*/
private function renderJsonResponse($json, $code = 200)
{
return new Response($json, $code, array('application/json'));
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Wallabag\CoreBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CommentBundle\Entity\Comment;
class LoadCommentData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* {@inheritdoc}
*/
public function load(ObjectManager $manager)
{
$comment1 = new Comment($this->getReference('admin-user'));
$comment1->setEntry($this->getReference('entry1'));
$comment1->setText('This is my comment /o/');
$comment1->setQuote('content');
$manager->persist($comment1);
$this->addReference('comment1', $comment1);
$comment2 = new Comment($this->getReference('admin-user'));
$comment2->setEntry($this->getReference('entry2'));
$comment2->setText('This is my 2nd comment /o/');
$comment2->setQuote('content');
$manager->persist($comment2);
$this->addReference('comment2', $comment2);
$manager->flush();
}
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 35;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Wallabag\CommentBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('wallabag_comment');
return $treeBuilder;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Wallabag\CommentBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class WallabagCommentExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}

View file

@ -0,0 +1,270 @@
<?php
namespace Wallabag\CommentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\VirtualProperty;
use JMS\Serializer\Annotation\SerializedName;
use Wallabag\UserBundle\Entity\User;
use Wallabag\CoreBundle\Entity\Entry;
/**
* Comment.
*
* @ORM\Table(name="comment")
* @ORM\Entity(repositoryClass="Wallabag\CommentBundle\Repository\CommentRepository")
* @ORM\HasLifecycleCallbacks()
* @ExclusionPolicy("none")
*/
class Comment
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="text", type="text")
*/
private $text;
/**
* @var \DateTime
*
* @ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @var string
*
* @ORM\Column(name="quote", type="string")
*/
private $quote;
/**
* @var array
*
* @ORM\Column(name="ranges", type="array")
*/
private $ranges;
/**
* @Exclude
*
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
*/
private $user;
/**
* @Exclude
*
* @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="comments")
* @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
*/
private $entry;
/*
* @param User $user
*/
public function __construct(\Wallabag\UserBundle\Entity\User $user)
{
$this->user = $user;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set text.
*
* @param string $text
*
* @return Comment
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get text.
*
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function timestamps()
{
if (is_null($this->createdAt)) {
$this->createdAt = new \DateTime();
}
$this->updatedAt = new \DateTime();
}
/**
* Get created.
*
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Get updated.
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Get quote.
*
* @return string
*/
public function getQuote()
{
return $this->quote;
}
/**
* Set quote.
*
* @param string $quote
*
* @return Comment
*/
public function setQuote($quote)
{
$this->quote = $quote;
return $this;
}
/**
* Get ranges.
*
* @return array
*/
public function getRanges()
{
return $this->ranges;
}
/**
* Set ranges.
*
* @param array $ranges
*
* @return Comment
*/
public function setRanges($ranges)
{
$this->ranges = $ranges;
return $this;
}
/**
* Set user.
*
* @param string $user
*
* @return Comment
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get user.
*
* @return string
*/
public function getUser()
{
return $this->user;
}
/**
* @VirtualProperty
* @SerializedName("user")
*/
public function getUserName()
{
return $this->user->getName();
}
/**
* Set entry.
*
* @param Entry $entry
*
* @return Comment
*/
public function setEntry($entry)
{
$this->entry = $entry;
$entry->setComment($this);
return $this;
}
/**
* Get entry.
*
* @return Entry
*/
public function getEntry()
{
return $this->entry;
}
/**
* @VirtualProperty
* @SerializedName("annotator_schema_version")
*/
public function getVersion()
{
return 'v1.0';
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Wallabag\CommentBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* CommentRepository.
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CommentRepository extends EntityRepository
{
/**
* Return a query builder to used by other getBuilderFor* method.
*
* @param int $userId
*
* @return QueryBuilder
*/
private function getBuilderByUser($userId)
{
return $this->createQueryBuilder('c')
->leftJoin('c.user', 'u')
->andWhere('u.id = :userId')->setParameter('userId', $userId)
->orderBy('c.id', 'desc')
;
}
/**
* Retrieves all comments for a user.
*
* @param int $userId
*
* @return QueryBuilder
*/
public function getBuilderForAllByUser($userId)
{
return $this
->getBuilderByUser($userId)
;
}
/**
* Get comment for this id.
*
* @param int $commentId
*
* @return array
*/
public function findCommentById($commentId)
{
return $this->createQueryBuilder('c')
->andWhere('c.id = :commentId')->setParameter('commentId', $commentId)
->getQuery()->getSingleResult()
;
}
/**
* Find comments for entry id.
*
* @param int $entryId
* @param int $userId
*
* @return array
*/
public function findCommentsByPageId($entryId, $userId)
{
return $this->createQueryBuilder('c')
->where('c.entry = :entryId')->setParameter('entryId', $entryId)
->andwhere('c.user = :userId')->setParameter('userId', $userId)
->getQuery()->getResult()
;
}
/**
* Find last comment for a given entry id. Used only for tests.
*
* @param int $entryId
*
* @return array
*/
public function findLastCommentByPageId($entryId, $userId)
{
return $this->createQueryBuilder('c')
->where('c.entry = :entryId')->setParameter('entryId', $entryId)
->andwhere('c.user = :userId')->setParameter('userId', $userId)
->orderBy('c.id', 'DESC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
}
}

View file

@ -0,0 +1,4 @@
annotations:
type: rest
resource: "WallabagCommentBundle:WallabagComment"
name_prefix: annotations_

View file

@ -0,0 +1,4 @@
services:
# wallabag_comment.example:
# class: Wallabag\CommentBundle\Example
# arguments: ["@service_id", "plain_value", %parameter%]

View file

@ -0,0 +1,81 @@
<?php
namespace Wallabag\CommentBundle\Tests\Controller;
use Wallabag\CommentBundle\Tests\WallabagCommentTestCase;
class CommentControllerTest extends WallabagCommentTestCase
{
public function testGetComments()
{
$comment = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCommentBundle:Comment')
->findOneBy(array('user' => 1));
if (!$comment) {
$this->markTestSkipped('No content found in db.');
}
$this->logInAs('admin');
$crawler = $this->client->request('GET', 'annotations/'.$comment->getEntry()->getId().'.json');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals(1, $content['total']);
$this->assertEquals($comment->getText(), $content['rows'][0]['text']);
}
public function testSetcomment()
{
$this->logInAs('admin');
$entry = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCoreBundle:Entry')
->findOneBy(array('user' => 1));
$headers = array('CONTENT_TYPE' => 'application/json');
$content = json_encode(array(
'text' => 'my comment',
'quote' => 'my quote',
'range' => '[{"start":"","startOffset":24,"end":"","endOffset":31}]',
));
$crawler = $this->client->request('POST', 'annotations/'.$entry->getId().'.json', array(), array(), $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$comment = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCommentBundle:Comment')
->findLastCommentByPageId($entry->getId(), 1);
$this->assertEquals('my comment', $comment->getText());
}
public function testEditcomment()
{
$comment = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCommentBundle:Comment')
->findOneBy(array('user' => 1));
$this->logInAs('admin');
$headers = array('CONTENT_TYPE' => 'application/json');
$content = json_encode(array(
'text' => 'a modified comment',
));
$crawler = $this->client->request('PUT', 'annotations/'.$comment->getId().'.json', array(), array(), $headers, $content);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertEquals('a modified comment', $content['text']);
$commentUpdated = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCommentBundle:Comment')
->findCommentById($comment->getId());
$this->assertEquals('a modified comment', $commentUpdated->getText());
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Wallabag\CommentBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
abstract class WallabagCommentTestCase extends WebTestCase
{
/**
* @var Client
*/
protected $client = null;
/**
* @var \FOS\UserBundle\Model\UserInterface
*/
protected $user;
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
public function logInAs($username)
{
$crawler = $this->client->request('GET', '/login');
$form = $crawler->filter('button[type=submit]')->form();
$data = array(
'_username' => $username,
'_password' => 'mypassword',
);
$this->client->submit($form, $data);
}
/**
* @return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$container = $client->getContainer();
/** @var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** @var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $container->getParameter('fos_user.firewall_name');
$this->user = $userManager->findUserBy(array('username' => 'admin'));
$loginManager->loginUser($firewallName, $this->user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken()));
$container->get('session')->save();
$session = $container->get('session');
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Wallabag\CommentBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class WallabagCommentBundle extends Bundle
{
}

View file

@ -9,6 +9,7 @@ use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlRoot;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
use Wallabag\CommentBundle\Entity\Comment;
/** /**
* Entry. * Entry.
@ -98,11 +99,10 @@ class Entry
private $updatedAt; private $updatedAt;
/** /**
* @var string * @ORM\OneToMany(targetEntity="Wallabag\CommentBundle\Entity\Comment", mappedBy="entry", cascade={"persist", "remove"})
* @ORM\JoinTable
* *
* @ORM\Column(name="comments", type="text", nullable=true) * @Groups({"entries_for_user", "export_all"})
*
* @Groups({"export_all"})
*/ */
private $comments; private $comments;
@ -366,7 +366,7 @@ class Entry
} }
/** /**
* @return string * @return ArrayCollection<Comment>
*/ */
public function getComments() public function getComments()
{ {
@ -374,11 +374,11 @@ class Entry
} }
/** /**
* @param string $comments * @param Comment $comment
*/ */
public function setComments($comments) public function setComment(Comment $comment)
{ {
$this->comments = $comments; $this->comments[] = $comment;
} }
/** /**

File diff suppressed because one or more lines are too long

View file

@ -38,11 +38,13 @@
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('bundles/wallabagcore/themes/_global/img/appicon/favicon.ico') }}"> <link rel="shortcut icon" type="image/x-icon" href="{{ asset('bundles/wallabagcore/themes/_global/img/appicon/favicon.ico') }}">
{% block css %}{% endblock %} {% block css %}
{% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery-2.0.3.min.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery-2.0.3.min.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery.cookie.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/jquery.cookie.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/bookmarklet.js') }}"></script> <script src="{{ asset('bundles/wallabagcore/themes/_global/js/bookmarklet.js') }}"></script>
<script src="{{ asset('bundles/wallabagcore/themes/_global/js/annotator.min.js') }}"></script>
{% endblock %} {% endblock %}
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>

View file

@ -177,6 +177,32 @@ main {
padding: 0; padding: 0;
} }
</style> </style>
<script type="text/javascript">
var app = new annotator.App();
app.include(annotator.ui.main, {
element: document.querySelector('article')
});
app.include(annotator.storage.http, {
prefix: '',
urls: {
create: '{{ path('annotations_post_annotation', { 'entry': entry.id }) }}',
update: '{{ path('annotations_put_annotation', { 'comment': 'idComment' }) }}',
destroy: '{{ path('annotations_delete_annotation', { 'comment': 'idComment' }) }}',
search: '{{ path('annotations_get_annotations', { 'entry': entry.id }) }}'
}
});
app
.start()
.then(function () {
app.annotations.load({entry: {{ entry.id }}});
});
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}