Implement comments
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
b4a1ae592f
commit
67ff9ff8db
@ -0,0 +1 @@
|
|||||||
|
import './bootstrap.js';
|
@ -0,0 +1,5 @@
|
|||||||
|
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||||
|
|
||||||
|
const app = startStimulusApp();
|
||||||
|
// register any custom, 3rd party controllers here
|
||||||
|
// app.register('some_controller_name', SomeImportedController);
|
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use App\Repository\CommentRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: CommentRepository::class)]
|
||||||
|
#[ORM\HasLifecycleCallbacks]
|
||||||
|
#[ApiResource(operations: [new GetCollection()])]
|
||||||
|
#[ApiFilter(filterClass: SearchFilter::class, properties: ['author', 'related_post'])]
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $author = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||||
|
private ?Post $related_post = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?\DateTimeImmutable $created_at = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?\DateTimeImmutable $edited_at = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $content = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthor(): ?User
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthor(?User $author): static
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedPost(): ?Post
|
||||||
|
{
|
||||||
|
return $this->related_post;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRelatedPost(?Post $related_post): static
|
||||||
|
{
|
||||||
|
$this->related_post = $related_post;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->created_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeImmutable $created_at): static
|
||||||
|
{
|
||||||
|
$this->created_at = $created_at;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->edited_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEditedAt(\DateTimeImmutable $edited_at): static
|
||||||
|
{
|
||||||
|
$this->edited_at = $edited_at;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): ?string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContent(string $content): static
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ORM\PrePersist]
|
||||||
|
public function setCreatedAtDate(): void
|
||||||
|
{
|
||||||
|
if ($this->created_at === null) {
|
||||||
|
$this->created_at = new \DateTimeImmutable();
|
||||||
|
$this->edited_at = $this->created_at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ORM\PreUpdate]
|
||||||
|
public function setEditedAtDate(): void
|
||||||
|
{
|
||||||
|
$this->edited_at = new \DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Comment;
|
||||||
|
use App\Entity\Post;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class CommentType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('content', TextareaType::class)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Comment::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Comment;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<Comment>
|
||||||
|
*/
|
||||||
|
class CommentRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Comment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Comment[] Returns an array of Comment objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('c')
|
||||||
|
// ->andWhere('c.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('c.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?Comment
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('c')
|
||||||
|
// ->andWhere('c.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security\Voter;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
class CommentVoter extends Voter
|
||||||
|
{
|
||||||
|
public const EDIT = 'COMMENT_EDIT';
|
||||||
|
|
||||||
|
protected function supports(string $attribute, mixed $subject): bool
|
||||||
|
{
|
||||||
|
// replace with your own logic
|
||||||
|
// https://symfony.com/doc/current/security/voters.html
|
||||||
|
return $attribute === self::EDIT
|
||||||
|
&& $subject instanceof \App\Entity\Comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
$user = $token->getUser();
|
||||||
|
|
||||||
|
// if the user is anonymous, do not grant access
|
||||||
|
if (!$user instanceof UserInterface) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($subject->getAuthor() === $user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array('ROLE_ADMIN', $user->getRoles(), true);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<form method="post" action="{{ path('app_post_comment_delete', {'id': comment.id}) }}" onsubmit="return confirm('Are you sure you want to delete this comment?');">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ comment.id) }}">
|
||||||
|
<button class="btn btn-danger">Delete</button>
|
||||||
|
</form>
|
@ -0,0 +1,17 @@
|
|||||||
|
<turbo-frame id="comment_{{ comment.id }}">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
{{ comment.author.email }} le {{ comment.createdAt | date }}
|
||||||
|
{% if comment.createdAt != comment.editedAt %}
|
||||||
|
(modifié le {{ comment.editedAt | date }})
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">{{ comment.content }}</p>
|
||||||
|
{% if is_granted('COMMENT_EDIT', comment) %}
|
||||||
|
<a href="{{ path('app_post_comment_edit', {'id': comment.id}) }}" class="btn btn-primary">Modifier</a>
|
||||||
|
{{ include('comment/_delete_form.html.twig') }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</turbo-frame>
|
@ -0,0 +1,3 @@
|
|||||||
|
{% block success_stream %}
|
||||||
|
<turbo-stream action="remove" target="comment_{{ comment }}"></turbo-stream>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Edit Comment{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Edit Comment</h1>
|
||||||
|
|
||||||
|
<turbo-frame id="comment_{{ comment.id }}">
|
||||||
|
{{ include('post/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
</turbo-frame>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% block success_stream %}
|
||||||
|
<turbo-stream action="append" targets="#comments">
|
||||||
|
<template>
|
||||||
|
{{ include('comment/comment.html.twig') }}
|
||||||
|
</template>
|
||||||
|
</turbo-stream>
|
||||||
|
{% endblock %}
|
@ -1,4 +1,4 @@
|
|||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form) }}
|
{{ form_widget(form) }}
|
||||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
<button class="btn btn-primary">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
Loading…
Reference in new issue