Commit a950a52e authored by Emma's avatar Emma 🦉

better serialisation, json submissions & comments

parent 953e23bb
Pipeline #51728183 passed with stages
in 7 minutes and 10 seconds
......@@ -5,6 +5,13 @@ comment_post:
methods: [POST]
requirements: { submission_id: "%number_regex%", comment_id: "%number_regex%" }
comment_json:
controller: App\Controller\CommentController::commentJson
defaults: { slug: '-' }
methods: [GET]
path: /f/{forum_name}/{submission_id}/{slug}/comment/{comment_id}.{_format}
requirements: { _format: json, submission_id: "%number_regex%", comment_id: "%number_regex%" }
edit_comment:
controller: App\Controller\CommentController::editComment
defaults: { slug: '-' }
......
......@@ -5,6 +5,12 @@ submission:
methods: [GET]
requirements: { submission_id: "%number_regex%" }
submission_json:
controller: App\Controller\SubmissionController::submissionJson
path: /f/{forum_name}/{submission_id}.{_format}
methods: [GET]
requirements: { _format: json, submission_id: "%number_regex%" }
submission_shortcut:
controller: App\Controller\SubmissionController::shortcut
path: /{id}
......
......@@ -123,6 +123,12 @@ final class CommentController extends AbstractController {
]);
}
public function commentJson(Forum $forum, Submission $submission, Comment $comment) {
return $this->json($comment, 200, [], [
'groups' => ['comment:read', 'abbreviated_relations'],
]);
}
/**
* Edits a comment.
*
......
......@@ -42,6 +42,12 @@ final class SubmissionController extends AbstractController {
]);
}
public function submissionJson(Forum $forum, Submission $submission) {
return $this->json($submission, 200, [], [
'groups' => ['submission:read', 'abbreviated_relations'],
]);
}
/**
* Show a single comment and its replies.
*
......
......@@ -7,6 +7,8 @@ use App\Entity\Exception\SubmissionLockedException;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
/**
* @ORM\Entity(repositoryClass="App\Repository\CommentRepository")
......@@ -18,6 +20,8 @@ class Comment extends Votable {
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Id()
*
* @Groups({"comment:read", "abbreviated_relations"})
*
* @var int
*/
private $id;
......@@ -25,6 +29,8 @@ class Comment extends Votable {
/**
* @ORM\Column(type="text")
*
* @Groups({"comment:read"})
*
* @var string
*/
private $body;
......@@ -32,6 +38,8 @@ class Comment extends Votable {
/**
* @ORM\Column(type="datetimetz")
*
* @Groups({"comment:read"})
*
* @var \DateTime
*/
private $timestamp;
......@@ -40,6 +48,8 @@ class Comment extends Votable {
* @ORM\JoinColumn(nullable=false)
* @ORM\ManyToOne(targetEntity="User", inversedBy="comments")
*
* @Groups({"comment:read"})
*
* @var User
*/
private $user;
......@@ -48,6 +58,8 @@ class Comment extends Votable {
* @ORM\JoinColumn(nullable=false)
* @ORM\ManyToOne(targetEntity="Submission", inversedBy="comments")
*
* @Groups({"comment:read"})
*
* @var Submission
*/
private $submission;
......@@ -77,6 +89,8 @@ class Comment extends Votable {
/**
* @ORM\Column(type="boolean", options={"default": false})
*
* @Groups({"comment:read"})
*
* @var bool
*/
private $softDeleted = false;
......@@ -91,6 +105,8 @@ class Comment extends Votable {
/**
* @ORM\Column(type="datetimetz", nullable=true)
*
* @Groups({"comment:read"})
*
* @var \DateTime|null
*/
private $editedAt;
......@@ -98,6 +114,8 @@ class Comment extends Votable {
/**
* @ORM\Column(type="boolean", options={"default": false})
*
* @Groups({"comment:read"})
*
* @var bool
*/
private $moderated = false;
......@@ -123,6 +141,21 @@ class Comment extends Votable {
*/
private $mentions;
/**
* @Groups({"comment:read"})
*/
protected $upvotes;
/**
* @Groups({"comment:read"})
*/
protected $downvotes;
/**
* @Groups({"comment:read"})
*/
protected $netScore;
public function __construct(
string $body,
User $user,
......@@ -191,6 +224,16 @@ class Comment extends Votable {
return $this->parent;
}
/**
* @Groups({"comment:read"})
* @SerializedName("parent")
*
* @return int|null
*/
public function getParentId(): ?int {
return $this->parent ? $this->parent->id : null;
}
/**
* Get replies, ordered by descending net score.
*
......@@ -206,6 +249,15 @@ class Comment extends Votable {
return $children;
}
/**
* @Groups({"comment:read"})
*
* @return int
*/
public function getReplyCount(): int {
return \count($this->children);
}
/**
* {@inheritdoc}
*/
......@@ -278,6 +330,16 @@ class Comment extends Votable {
return $this->userFlag;
}
/**
* @Groups({"comment:read"})
* @SerializedName("userFlag")
*
* @return string|null
*/
public function getReadableUserFlag(): ?string {
return UserFlags::toReadable($this->userFlag);
}
public function setUserFlag(int $userFlag) {
if (!in_array($userFlag, UserFlags::FLAGS, true)) {
throw new \InvalidArgumentException('Bad flag');
......
......@@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping as ORM;
use Pagerfanta\Adapter\DoctrineCollectionAdapter;
use Pagerfanta\Adapter\DoctrineSelectableAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ORM\Entity(repositoryClass="App\Repository\ForumRepository")
......@@ -26,6 +27,8 @@ class Forum {
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Id()
*
* @Groups({"abbreviated_relations"})
*
* @var int|null
*/
private $id;
......@@ -33,6 +36,8 @@ class Forum {
/**
* @ORM\Column(type="text", unique=true)
*
* @Groups({"abbreviated_relations"})
*
* @var string
*/
private $name;
......
......@@ -7,6 +7,8 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
/**
* @ORM\Entity(repositoryClass="App\Repository\SubmissionRepository")
......@@ -27,6 +29,8 @@ class Submission extends Votable {
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Id()
*
* @Groups({"submission:read", "abbreviated_relations"})
*
* @var int
*/
private $id;
......@@ -34,6 +38,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="text")
*
* @Groups({"submission:read"})
*
* @var string
*/
private $title;
......@@ -41,6 +47,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="text", nullable=true)
*
* @Groups({"submission:read"})
*
* @var string
*/
private $url;
......@@ -48,6 +56,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="text", nullable=true)
*
* @Groups({"submission:read"})
*
* @var string
*/
private $body;
......@@ -63,6 +73,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="datetimetz")
*
* @Groups({"submission:read"})
*
* @var \DateTime
*/
private $timestamp;
......@@ -71,6 +83,8 @@ class Submission extends Votable {
* @ORM\JoinColumn(nullable=false)
* @ORM\ManyToOne(targetEntity="Forum", inversedBy="submissions")
*
* @Groups({"submission:read", "abbreviated_relations"})
*
* @var Forum
*/
private $forum;
......@@ -79,6 +93,8 @@ class Submission extends Votable {
* @ORM\JoinColumn(nullable=false)
* @ORM\ManyToOne(targetEntity="User", inversedBy="submissions")
*
* @Groups({"submission:read"})
*
* @var User
*/
private $user;
......@@ -115,6 +131,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="boolean")
*
* @Groups({"submission:read"})
*
* @var bool
*/
private $sticky = false;
......@@ -129,6 +147,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="datetimetz", nullable=true)
*
* @Groups({"submission:read"})
*
* @var \DateTime|null
*/
private $editedAt;
......@@ -136,6 +156,8 @@ class Submission extends Votable {
/**
* @ORM\Column(type="boolean", options={"default": false})
*
* @Groups({"submission:read"})
*
* @var bool
*/
private $moderated = false;
......@@ -150,10 +172,27 @@ class Submission extends Votable {
/**
* @ORM\Column(type="boolean", options={"default": false})
*
* @Groups({"submission:read"})
*
* @var bool
*/
private $locked = false;
/**
* @Groups({"submission:read"})
*/
protected $upvotes;
/**
* @Groups({"submission:read"})
*/
protected $downvotes;
/**
* @Groups({"submission:read"})
*/
protected $netScore;
public function __construct(
string $title,
?string $url,
......@@ -223,6 +262,11 @@ class Submission extends Votable {
return $this->comments;
}
/**
* @Groups({"submission:read"})
*
* @return int
*/
public function getCommentCount(): int {
return \count($this->comments);
}
......@@ -363,6 +407,16 @@ class Submission extends Votable {
return $this->userFlag;
}
/**
* @Groups({"submission:read"})
* @SerializedName("userFlag")
*
* @return string|null
*/
public function getReadableUserFlag(): ?string {
return UserFlags::toReadable($this->userFlag);
}
public function setUserFlag(int $userFlag) {
if (!in_array($userFlag, UserFlags::FLAGS, true)) {
throw new \InvalidArgumentException('Bad flag');
......
......@@ -12,6 +12,7 @@ use Pagerfanta\Adapter\DoctrineSelectableAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
......@@ -40,6 +41,8 @@ class User implements UserInterface, EquatableInterface {
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Id()
*
* @Groups({"abbreviated_relations"})
*
* @var int|null
*/
private $id;
......@@ -47,6 +50,8 @@ class User implements UserInterface, EquatableInterface {
/**
* @ORM\Column(type="text", unique=true)
*
* @Groups({"abbreviated_relations"})
*
* @var string
*/
private $username;
......
......@@ -94,7 +94,7 @@ final class WebhookListener implements EventSubscriberInterface {
$body = $this->serializer->serialize([
'event' => $webhook->getEvent(),
'subject' => $item['subject'],
], 'json');
], 'json', $item['context']);
yield new GuzzleRequest('POST', $webhook->getUrl(), $headers ?? [], $body);
}
......@@ -118,7 +118,9 @@ final class WebhookListener implements EventSubscriberInterface {
$subject = $event->getSubject();
$forum = $subject->getForum();
$this->addToQueue($forum, ForumWebhook::EVENT_NEW_SUBMISSION, $subject);
$this->addToQueue($forum, ForumWebhook::EVENT_NEW_SUBMISSION, $subject, [
'groups' => ['submission:read', 'abbreviated_relations'],
]);
}
public function onEditSubmission(EntityModifiedEvent $event): void {
......@@ -129,6 +131,8 @@ final class WebhookListener implements EventSubscriberInterface {
$this->addToQueue($forum, ForumWebhook::EVENT_EDIT_SUBMISSION, [
'before' => $event->getBefore(),
'after' => $event->getAfter(),
], [
'groups' => ['submission:read', 'abbreviated_relations'],
]);
}
......@@ -137,7 +141,9 @@ final class WebhookListener implements EventSubscriberInterface {
$subject = $event->getSubject();
$forum = $subject->getSubmission()->getForum();
$this->addToQueue($forum, ForumWebhook::EVENT_NEW_COMMENT, $subject);
$this->addToQueue($forum, ForumWebhook::EVENT_NEW_COMMENT, $subject, [
'groups' => ['comment:read', 'abbreviated_relations'],
]);
}
public function onEditComment(EntityModifiedEvent $event): void {
......@@ -148,6 +154,8 @@ final class WebhookListener implements EventSubscriberInterface {
$this->addToQueue($forum, ForumWebhook::EVENT_EDIT_COMMENT, [
'before' => $event->getBefore(),
'after' => $event->getAfter(),
], [
'groups' => ['comment:read', 'abbreviated_relations'],
]);
}
......@@ -163,12 +171,12 @@ final class WebhookListener implements EventSubscriberInterface {
return $queue;
}
private function addToQueue(Forum $forum, string $eventName, $subject): void {
private function addToQueue(Forum $forum, string $eventName, $subject, array $context): void {
$webhooks = $forum->getWebhooksByEvent($eventName);
$queue = $this->getQueue();
foreach ($webhooks as $webhook) {
$queue->push(['webhook' => $webhook, 'subject' => $subject]);
$queue->push(['webhook' => $webhook, 'subject' => $subject, 'context' => $context]);
}
}
......
<?php
namespace App\Serializer;
use App\Entity\Comment;
use App\Entity\UserFlags;
use App\Utils\Slugger;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class CommentNormalizer extends AbstractNormalizer {
/**
* @var UrlGeneratorInterface
*/
private $urlGenerator;
public function __construct(
UrlGeneratorInterface $urlGenerator,
ClassMetadataFactoryInterface $classMetadataFactory = null,
NameConverterInterface $nameConverter = null
) {
parent::__construct($classMetadataFactory, $nameConverter);
$this->urlGenerator = $urlGenerator;
}
public function denormalize($data, $class, $format = null, array $context = []) {
// TODO: Implement denormalize() method.
}
public function supportsDenormalization($data, $type, $format = null): bool {
return false;
}
public function normalize($object, $format = null, array $context = []): array {
if (!$object instanceof Comment) {
throw new \InvalidArgumentException();
}
$normalized = [
'resource' => $this->urlGenerator->generate('comment', [
'forum_name' => $object->getSubmission()->getForum()->getName(),
'submission_id' => $object->getSubmission()->getId(),
'comment_id' => $object->getId(),
], UrlGeneratorInterface::ABSOLUTE_URL),
'id' => $object->getId(),
'body' => $object->getBody(),
'timestamp' => $object->getTimestamp()->format('c'),
'user' => $this->urlGenerator->generate('user', [
'username' => $object->getUser()->getUsername(),
], UrlGeneratorInterface::ABSOLUTE_URL),
'submission' => $this->urlGenerator->generate('submission', [
'forum_name' => $object->getSubmission()->getForum()->getName(),
'submission_id' => $object->getSubmission()->getId(),
'slug' => Slugger::slugify($object->getSubmission()->getTitle()),
], UrlGeneratorInterface::ABSOLUTE_URL),
'parent' => $object->getParent()
? $this->urlGenerator->generate('comment', [
'forum_name' => $object->getSubmission()->getForum()->getName(),
'submission_id' => $object->getSubmission()->getId(),
'comment_id' => $object->getParent()->getId(),
], UrlGeneratorInterface::ABSOLUTE_URL)
: null,
'reply_count' => \count($object->getChildren()),
'upvotes' => $object->getUpvotes(),
'downvotes' => $object->getDownvotes(),
'soft_deleted' => $object->isSoftDeleted(),
'edited_at' => $object->getEditedAt()
? $object->getEditedAt()->format('c')
: null,
'moderated' => $object->isModerated(),
'user_flag' => UserFlags::toReadable($object->getUserFlag()),
];
return \array_filter($normalized, function ($element) {
return $element !== null;
});
}
public function supportsNormalization($data, $format = null): bool {
return $data instanceof Comment;
}
}
......@@ -3,94 +3,52 @@
namespace App\Serializer;
use App\Entity\Submission;
use App\Entity\UserFlags;
use App\Utils\Slugger;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class SubmissionNormalizer extends AbstractNormalizer {
class SubmissionNormalizer implements NormalizerInterface {
/**
* @var UrlGeneratorInterface
* @var CacheManager
*/
private $urlGenerator;
private $cacheManager;
/**
* @var CacheManager
* @var ObjectNormalizer
*/
private $cacheManager;
private $normalizer;
public function __construct(
UrlGeneratorInterface $urlGenerator,
CacheManager $liipCacheManager,
ClassMetadataFactoryInterface $classMetadataFactory = null,
NameConverterInterface $nameConverter = null
CacheManager $cacheManager,
ObjectNormalizer $normalizer
) {
parent::__construct($classMetadataFactory, $nameConverter);
$this->urlGenerator = $urlGenerator;
$this->cacheManager = $liipCacheManager;
}
public function denormalize($data, $class, $format = null, array $context = []) {
// TODO
}
public function supportsDenormalization($data, $type, $format = null): bool {
return false;
$this->cacheManager = $cacheManager;
$this->normalizer = $normalizer;
}
public function normalize($object, $format = null, array $context = []): array {
if (!$object instanceof Submission) {
throw new \InvalidArgumentException();
throw new \InvalidArgumentException('Expected $object to be instance of '.Submission::class);
}
$normalized = [
'resource' => $this->urlGenerator->generate('submission', [
'forum_name' => $object->getForum()->getName(),
'submission_id' => $object->getId(),
'slug' => Slugger::slugify($object->getTitle()),
], UrlGeneratorInterface::ABSOLUTE_URL),
'id' => $object->getId(),
'forum' => $this->urlGenerator->generate('forum', [
'forum_name' => $object->getForum()->getName(),
], UrlGeneratorInterface::ABSOLUTE_URL),
'user' => $this->urlGenerator->generate('user', [
'username' => $object->getUser()->getUsername(),
], UrlGeneratorInterface::ABSOLUTE_URL),
'title' => $object->getTitle(),
'body' => $object->getBody(),
'url' => $object->getUrl(),
'timestamp' => $object->getTimestamp()->format('c'),
'locked' => $object->isLocked(),
'sticky' => $object->isSticky(),
'user_flag' => UserFlags::toReadable($object->getUserFlag()),
'edited_at' => $object->getEditedAt()
? $object->getEditedAt()->format('c')
: null,
'moderated' => $object->isModerated(),
'comment_count' => \count($object->getComments()),
'upvotes' => $object->getUpvotes(),
'downvotes' => $object->getDownvotes(),
];
$data = $this->normalizer->normalize($object, $format, $context);
if (\in_array('submission:read', $context['groups'] ?? [], true)) {
$image = $object->getImage();
if ($object->getImage()) {
$normalized['thumbnail_1x'] = $this->cacheManager->generateUrl(
$object->getImage(),
'submission_thumbnail_1x'
);
foreach (['1x', '2x'] as $size) {
if ($image) {
$url = $this->cacheManager->generateUrl(
$image,
"submission_thumbnail_{$size}"
);
}
$normalized['thumbnail_2x'] = $this->cacheManager->generateUrl(
$object->getImage(),
'submission_thumbnail_2x'
);
$data["thumbnail_{$size}"] = $url ?? null;
}
}
return \array_filter($normalized, function ($element) {
return $element !== null;
});
return $data;
}
public function supportsNormalization($data, $format = null): bool {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment