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_widget(form) }}
|
||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||
<button class="btn btn-primary">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
||||
|
Loading…
Reference in new issue