From 16b232e96a6bab52928de095c0a2155b32760fcd Mon Sep 17 00:00:00 2001
From: pfleu <fleutotp@gmail.com>
Date: Fri, 16 Dec 2022 19:09:17 +0100
Subject: [PATCH] =?UTF-8?q?API:=20requ=C3=AAtes=20pour=20recherche=20de=20?=
 =?UTF-8?q?traces,=20recherche=20de=20logs=20avec=20divers=20filtres.=20re?=
 =?UTF-8?q?qu=C3=AAtes=20de=20modification=20des=20commentaires=20ou=20de?=
 =?UTF-8?q?=20la=20discussion=20pour=20les=20entr=C3=A9es=20te=20pour=20le?=
 =?UTF-8?q?s=20lexiques.=20Ajout=20fonction=20SQL=20Date=20pour=20recherch?=
 =?UTF-8?q?e=20des=20logs/traces=20par=20date.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 config/packages/doctrine.yaml           |   4 +
 src/Controller/ApiEntryController.php   |  92 +++++++++++++-
 src/Controller/ApiLabelController.php   |   2 +-
 src/Controller/ApiLexiconController.php |  92 +++++++++++++-
 src/Controller/ApiLogController.php     | 155 ++++++++++++++++++++++++
 src/Controller/ApiTraceController.php   |  63 ++++++++++
 src/Controller/LexiconController.php    |   3 +-
 src/Doctrine/DQL/Date.php               |  25 ++++
 src/Doctrine/DQL/Year.php               |  25 ++++
 src/Entity/Entry.php                    |   8 +-
 src/Entity/Log.php                      |  11 +-
 src/Entity/LoggableTrait.php            |  10 +-
 src/Entity/Trace.php                    |   4 +-
 src/Repository/LogRepository.php        | 104 ++++++++++++++++
 src/Repository/TraceRepository.php      |  98 +++++++++++++++
 15 files changed, 680 insertions(+), 16 deletions(-)
 create mode 100644 src/Controller/ApiLogController.php
 create mode 100644 src/Doctrine/DQL/Date.php
 create mode 100644 src/Doctrine/DQL/Year.php
 create mode 100644 src/Repository/LogRepository.php
 create mode 100644 src/Repository/TraceRepository.php

diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml
index 8fe1dd4..95228da 100644
--- a/config/packages/doctrine.yaml
+++ b/config/packages/doctrine.yaml
@@ -15,6 +15,10 @@ doctrine:
                 dir: '%kernel.project_dir%/src/Entity'
                 prefix: 'App\Entity'
                 alias: App
+        dql:
+            datetime_functions:
+                date: App\Doctrine\DQL\Date
+                year: App\Doctrine\DQL\Year
 
 when@test:
     doctrine:
diff --git a/src/Controller/ApiEntryController.php b/src/Controller/ApiEntryController.php
index 00c6593..71f78c8 100644
--- a/src/Controller/ApiEntryController.php
+++ b/src/Controller/ApiEntryController.php
@@ -62,7 +62,7 @@ class ApiEntryController extends ApiBaseController
     /**
      * Recherche d'une entrée par graphie et par langue, dans un ensemble de lexiques
      *
-     * @Route("/search", name="api_search", methods={"POST"})
+     * @Route("/search", name="api_entry_search", methods={"POST"})
      *
      * @OA\Response(response=200, description="success",
      *      @OA\JsonContent(type="array",
@@ -405,6 +405,96 @@ class ApiEntryController extends ApiBaseController
         return new JsonResponse($updatedEntryData, 200, [], true);
     }
 
+    /**
+     * @Route("/edit-comment/{id}", name="api_edit_comment_entry", methods={"PUT"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     *
+     * @OA\Parameter(
+     *     name="id",
+     *     in="path",
+     *     description="id of the entry to edit",
+     *     @OA\Schema(type="string")
+     * )
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *       required={"comment"},
+     *       @OA\Property(property="comment", type="string", example="bloc de commentaires dans sa totalité"),
+     *     )
+     * )
+     * @OA\Tag(name="Entries")
+     * @Security(name="OAuth2")
+     */
+    public function editCommentEntry(Request $request, SerializerInterface $serializer, Entry $entry = null)
+    {
+        if ($entry === null) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Pas d'entrée trouvée pour cette Id")]);
+        }
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if ($missingFields = $this->getMissingFields($data, ['comment'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]);
+        }
+
+        $entry->setCommentsBy($data['comment'], $this->getUser());
+        $this->doctrine->getManager()->flush();
+
+        $this->addSuccessMessage(sprintf("Commentaires de l'entrée %s modifiés", $entry->getId()));
+
+        return $this->createJsonResponse();
+    }
+
+    /**
+     * @Route("/edit-discussion/{id}", name="api_edit_discussion_entry", methods={"PUT"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     *
+     * @OA\Parameter(
+     *     name="id",
+     *     in="path",
+     *     description="id of the entry to edit",
+     *     @OA\Schema(type="string")
+     * )
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *       required={"discussion"},
+     *       @OA\Property(property="discussion", type="string", example="bloc de discussion dans sa totalité"),
+     *     )
+     * )
+     * @OA\Tag(name="Entries")
+     * @Security(name="OAuth2")
+     */
+    public function editDiscussionEntry(Request $request, SerializerInterface $serializer, Entry $entry = null)
+    {
+        if ($entry === null) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Pas d'entrée trouvée pour cette Id")]);
+        }
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if ($missingFields = $this->getMissingFields($data, ['discussion'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]);
+        }
+
+        $entry->setDiscussionBy($data['discussion'], $this->getUser());
+        $this->doctrine->getManager()->flush();
+
+        $this->addSuccessMessage(sprintf("Discussion de l'entrée %s modifiée", $entry->getId()));
+
+        return $this->createJsonResponse();
+    }
+
     /**
      * @Route("/delete", name="api_entry_delete", methods={"DELETE"})
      *
diff --git a/src/Controller/ApiLabelController.php b/src/Controller/ApiLabelController.php
index 509e906..a6f9e3b 100644
--- a/src/Controller/ApiLabelController.php
+++ b/src/Controller/ApiLabelController.php
@@ -331,7 +331,7 @@ class ApiLabelController extends ApiBaseController
     {
 //        $labelId = $request->get('labelId');
 //        if (!$labelId) {
-//            return $this->createJsonResponse(401, ['error' => sprintf("Le paramètre labelId ets obligatoire")]);
+//            return $this->createJsonResponse(401, ['error' => sprintf("Le paramètre labelId est obligatoire")]);
 //        }
 //        $label = $this->doctrine->getRepository(Label::class)->find($labelId);
 //        if (!$label) {
diff --git a/src/Controller/ApiLexiconController.php b/src/Controller/ApiLexiconController.php
index e2e2def..a1b6109 100644
--- a/src/Controller/ApiLexiconController.php
+++ b/src/Controller/ApiLexiconController.php
@@ -148,7 +148,7 @@ class ApiLexiconController extends ApiBaseController
     {
         $lexiconId = $request->get('lexiconId');
         if (!$lexiconId) {
-            return $this->createJsonResponse(401, ['error' => sprintf("Le paramètre lexiconId ets obligatoire")]);
+            return $this->createJsonResponse(401, ['error' => sprintf("Le paramètre lexiconId est obligatoire")]);
         }
         $lexicon = $this->doctrine->getRepository(Lexicon::class)->find($lexiconId);
         if (!$lexicon) {
@@ -167,4 +167,94 @@ class ApiLexiconController extends ApiBaseController
 
         return $this->createJsonResponse(200, $selectedGraphies);
     }
+
+    /**
+     * @Route("/edit-comment/{id}", name="api_edit_comment_lexicon", methods={"PUT"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     *
+     * @OA\Parameter(
+     *     name="id",
+     *     in="path",
+     *     description="id of the lexicon to edit",
+     *     @OA\Schema(type="string")
+     * )
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *       required={"comment"},
+     *       @OA\Property(property="comment", type="string", example="bloc de commentaires dans sa totalité"),
+     *     )
+     * )
+     * @OA\Tag(name="Lexicons")
+     * @Security(name="OAuth2")
+     */
+    public function editCommentLexicon(Request $request, SerializerInterface $serializer, Lexicon $lexicon = null)
+    {
+        if ($lexicon === null) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Pas de lexique trouvé pour cette Id")]);
+        }
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if ($missingFields = $this->getMissingFields($data, ['comment'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]);
+        }
+
+        $lexicon->setCommentsBy($data['comment'], $this->getUser());
+        $this->doctrine->getManager()->flush();
+
+        $this->addSuccessMessage(sprintf("Commentaires du lexique %s modifiés", $lexicon->getId()));
+
+        return $this->createJsonResponse();
+    }
+
+    /**
+     * @Route("/edit-discussion/{id}", name="api_edit_discussion_lexicon", methods={"PUT"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     *
+     * @OA\Parameter(
+     *     name="id",
+     *     in="path",
+     *     description="id of the lexicon to edit",
+     *     @OA\Schema(type="string")
+     * )
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *       required={"discussion"},
+     *       @OA\Property(property="discussion", type="string", example="bloc de discussion dans sa totalité"),
+     *     )
+     * )
+     * @OA\Tag(name="Lexicons")
+     * @Security(name="OAuth2")
+     */
+    public function editDiscussionLexicon(Request $request, SerializerInterface $serializer, Lexicon $lexicon = null)
+    {
+        if ($lexicon === null) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Pas de lexique trouvée pour cette Id")]);
+        }
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if ($missingFields = $this->getMissingFields($data, ['discussion'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]);
+        }
+
+        $lexicon->setDiscussionBy($data['discussion'], $this->getUser());
+        $this->doctrine->getManager()->flush();
+
+        $this->addSuccessMessage(sprintf("Discussion du lexique %s modifiée", $lexicon->getId()));
+
+        return $this->createJsonResponse();
+    }
 }
diff --git a/src/Controller/ApiLogController.php b/src/Controller/ApiLogController.php
new file mode 100644
index 0000000..63be464
--- /dev/null
+++ b/src/Controller/ApiLogController.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace App\Controller;
+
+use App\Entity\Entry;
+use App\Entity\Graphy;
+use App\Entity\Headword;
+use App\Entity\Label;
+use App\Entity\Lexicon;
+use App\Entity\Log;
+use App\Entity\User;
+use App\Manager\WiktionaryManager;
+use App\Repository\LabelRepository;
+use Doctrine\Persistence\ManagerRegistry;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+use Nelmio\ApiDocBundle\Annotation\Security;
+use OpenApi\Annotations as OA;
+use Nelmio\ApiDocBundle\Annotation\Model;
+use Symfony\Component\Serializer\SerializerInterface;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+
+/**
+ * @Route("/api/log")
+ */
+class ApiLogController extends ApiBaseController
+{
+//    /**
+//     *
+//     * @Route("/create", name="api_log_create", methods={"POST"})
+//     *
+//     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+//     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+//     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+//     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+//     * @OA\RequestBody(
+//     *     required=true,
+//     *     @OA\JsonContent(
+//     *       required={"origin", "action"},
+//     *       @OA\Property(property="origin", type="string", example="Prisms"),
+//     *       @OA\Property(property="action", type="string", example="partie annulée"),
+//     *       @OA\Property(property="additional_info", type="object"),
+//     *     )
+//     * )
+//     * @OA\Tag(name="Logs")
+//     * @Security(name="OAuth2")
+//     */
+//    public function createLog(Request $request): Response
+//    {
+//        $data = json_decode($request->getContent(), true);
+//        if (!$data) {
+//            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+//        }
+//        if ($missingFields = $this->getMissingFields($data, ['origin', 'action'])) {
+//            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]);
+//        }
+//
+//        $log = new Log();
+//        $log->setCreatedBy($this->getUser());
+//        $log->setAction($data['action']);
+//        $log->setOrigin($data['origin']);
+//        if ($data['additional_info'] ?? null) {
+//            $log->setAdditionalInfo($data['additional_info']);
+//        }
+//        $this->doctrine->getManager()->persist($log);
+//        $this->doctrine->getManager()->flush();
+//
+//        $this->success[] = sprintf("Log id %s créée.", $log->getId());
+//
+//        return $this->createJsonResponse(200);
+//    }
+
+    /**
+     * Recherche de logs par date ou user
+     *
+     * @Route("/search", name="api_log_search", methods={"POST"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *         @OA\Property(property="date", type="date"),
+     *         @OA\Property(property="category", type="string", example="update_comment OU update_discussion"),
+     *         @OA\Property(property="user_id", type="integer", example="4"),
+     *         @OA\Property(property="lexicon_id", type="integer", example="4"),
+     *         @OA\Property(property="entry_id", type="integer", example="4"),
+     *     )
+     * )
+     * @OA\Tag(name="Logs")
+     * @Security(name="OAuth2")
+     */
+    public function search(Request $request, SerializerInterface $serializer): Response
+    {
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if (!isset($data['date']) && !isset($data['user_id']) && !isset($data['lexicon_id']) && !isset($data['entry_id'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez spécifier au moins un paramètre de recherche parmi: date, user_id, lexicon_id, entry_id")]);
+        }
+        if (($data['category'] ?? null) && !in_array($data['category'], Log::LOG_LIST_CATEGORIES)) {
+            return $this->createJsonResponse(401, ['error' => sprintf("La catégorie de log %s n'existe pas", $data['category'])]);
+        }
+
+        $filter = [];
+        if ($data["lexicon_id"] ?? null) {
+            $lexicon = $this->doctrine->getRepository(Lexicon::class)->find($data['lexicon_id']);
+            if (!$lexicon) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Pas de lexique trouvé pour l'id %s", $data['lexicon_id'])]);
+            } else {
+                $filter['lexicon'] = $lexicon;
+            }
+        }
+        if ($data["entry_id"] ?? null) {
+            $entry = $this->doctrine->getRepository(Entry::class)->find($data['entry_id']);
+            if (!$entry) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Pas d'entrée trouvée pour l'id %s", $data['entry_id'])]);
+            } else {
+                $filter['entry'] = $entry;
+            }
+        }
+        if ($data['date'] ?? null) {
+            $date = date_create_from_format('Y-m-d', $data['date']);
+            if ($date === false) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une date au format aaaa-mm-jj pour le paramètre «date»")]);
+            } else {
+                $filter['date'] = $date;
+            }
+        }
+        if ($data["user_id"] ?? null) {
+            $user = $this->doctrine->getRepository(User::class)->find($data['user_id']);
+            if (!$user) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Pas d'utilisateur trouvé pour l'id %s", $data['user_id'])]);
+            } else {
+                $filter['user'] = $user;
+            }
+        }
+        if ($data["category"] ?? null) {
+            $filter['category'] = $data["category"];
+        }
+
+        $logs = $this->doctrine->getRepository(Log::class)->search($filter);
+
+        $content = $serializer->serialize($logs, 'json', ['groups' => ["log:read"]]);
+
+        return new JsonResponse($content, 200, [], true);
+    }
+}
diff --git a/src/Controller/ApiTraceController.php b/src/Controller/ApiTraceController.php
index 7e23ec5..049767c 100644
--- a/src/Controller/ApiTraceController.php
+++ b/src/Controller/ApiTraceController.php
@@ -8,6 +8,7 @@ use App\Entity\Headword;
 use App\Entity\Label;
 use App\Entity\Lexicon;
 use App\Entity\Trace;
+use App\Entity\User;
 use App\Manager\WiktionaryManager;
 use App\Repository\LabelRepository;
 use Doctrine\Persistence\ManagerRegistry;
@@ -72,4 +73,66 @@ class ApiTraceController extends ApiBaseController
 
         return $this->createJsonResponse(200);
     }
+
+    /**
+     * Recherche de traces par date ou user ou action ou origin
+     *
+     * @Route("/search", name="api_trace_search", methods={"POST"})
+     *
+     * @OA\Response(response=200, description="success", @OA\JsonContent(type="string"))
+     * @OA\Response(response=401, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=403, description="error", @OA\JsonContent(type="string"))
+     * @OA\Response(response=500, description="error", @OA\JsonContent(type="string"))
+     * @OA\RequestBody(
+     *     required=true,
+     *     @OA\JsonContent(
+     *         @OA\Property(property="date", type="date"),
+     *         @OA\Property(property="user_id", type="integer", example="4"),
+     *         @OA\Property(property="origin", type="string", example="Prisms"),
+     *         @OA\Property(property="action", type="string", example="action"),
+     *     )
+     * )
+     * @OA\Tag(name="Traces")
+     * @Security(name="OAuth2")
+     */
+    public function search(Request $request, SerializerInterface $serializer): Response
+    {
+        $data = json_decode($request->getContent(), true);
+        if (!$data) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]);
+        }
+        if (!isset($data['date']) && !isset($data['user_id']) && !isset($data['origin']) && !isset($data['action'])) {
+            return $this->createJsonResponse(401, ['error' => sprintf("Veuillez spécifier au moins un paramètre de recherche parmi: date, user_id, action, origin")]);
+        }
+
+        $filter = [];
+        if ($data['date'] ?? null) {
+            $date = date_create_from_format('Y-m-d', $data['date']);
+            if ($date === false) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une date au format aaaa-mm-jj pour le paramètre «date»")]);
+            } else {
+                $filter['date'] = $date;
+            }
+        }
+        if ($data["user_id"] ?? null) {
+            $user = $this->doctrine->getRepository(User::class)->find($data['user_id']);
+            if (!$user) {
+                return $this->createJsonResponse(401, ['error' => sprintf("Pas d'utilisateur trouvé pour l'id %s", $data['user_id'])]);
+            } else {
+                $filter['user'] = $user;
+            }
+        }
+        if ($data['origin'] ?? null) {
+            $filter['origin'] = $data['origin'];
+        }
+        if ($data['action'] ?? null) {
+            $filter['action'] = $data['action'];
+        }
+
+        $traces = $this->doctrine->getRepository(Trace::class)->search($filter);
+
+        $content = $serializer->serialize($traces, 'json', ['groups' => ["trace:read"]]);
+
+        return new JsonResponse($content, 200, [], true);
+    }
 }
diff --git a/src/Controller/LexiconController.php b/src/Controller/LexiconController.php
index e6e544f..ab248f9 100644
--- a/src/Controller/LexiconController.php
+++ b/src/Controller/LexiconController.php
@@ -4,6 +4,7 @@ namespace App\Controller;
 
 use App\Entity\Entry;
 use App\Entity\Lexicon;
+use App\Entity\Log;
 use App\Repository\LexiconRepository;
 use Doctrine\Persistence\ManagerRegistry;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
@@ -23,7 +24,7 @@ class LexiconController extends AbstractController
      */
     public function index(LexiconRepository $lexiconRepository): Response
     {
-//        $entry1 = $this->getDoctrine()->getRepository(Entry::class)->find(1);
+        dump(in_array('update_comment', Log::LOG_LIST_CATEGORIES));die();
 //        $entry2 = $this->getDoctrine()->getRepository(Entry::class)->find(9);
 //
 //
diff --git a/src/Doctrine/DQL/Date.php b/src/Doctrine/DQL/Date.php
new file mode 100644
index 0000000..24d2137
--- /dev/null
+++ b/src/Doctrine/DQL/Date.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Doctrine\DQL;
+
+use Doctrine\ORM\Query\Lexer;
+use Doctrine\ORM\Query\AST\Functions\FunctionNode;
+
+class Date extends FunctionNode
+{
+    public $date;
+
+    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
+    {
+        return "DATE(" . $sqlWalker->walkArithmeticPrimary($this->date) . ")";
+    }
+    public function parse(\Doctrine\ORM\Query\Parser $parser)
+    {
+        $parser->match(Lexer::T_IDENTIFIER);
+        $parser->match(Lexer::T_OPEN_PARENTHESIS);
+
+        $this->date = $parser->ArithmeticPrimary();
+
+        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
+    }
+}
diff --git a/src/Doctrine/DQL/Year.php b/src/Doctrine/DQL/Year.php
new file mode 100644
index 0000000..c6f158f
--- /dev/null
+++ b/src/Doctrine/DQL/Year.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Doctrine\DQL;
+
+use Doctrine\ORM\Query\Lexer;
+use Doctrine\ORM\Query\AST\Functions\FunctionNode;
+
+class Year extends FunctionNode
+{
+    public $year;
+
+    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
+    {
+        return "YEAR(" . $sqlWalker->walkArithmeticPrimary($this->year) . ")";
+    }
+    public function parse(\Doctrine\ORM\Query\Parser $parser)
+    {
+        $parser->match(Lexer::T_IDENTIFIER);
+        $parser->match(Lexer::T_OPEN_PARENTHESIS);
+
+        $this->year = $parser->ArithmeticPrimary();
+
+        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
+    }
+}
diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php
index 945fcf5..ba7eff9 100644
--- a/src/Entity/Entry.php
+++ b/src/Entity/Entry.php
@@ -80,7 +80,7 @@ class Entry
     private $lexicon;
 
     /**
-     * @ORM\OneToMany(targetEntity=Log::class, mappedBy="lexicon", cascade={"remove", "persist"})
+     * @ORM\OneToMany(targetEntity=Log::class, mappedBy="entry", cascade={"remove", "persist"})
      */
     private $logs;
 
@@ -231,7 +231,7 @@ class Entry
     {
         if (!$this->logs->contains($log)) {
             $this->logs[] = $log;
-            $log->setLexicon($this);
+            $log->setEntry($this);
         }
 
         return $this;
@@ -241,8 +241,8 @@ class Entry
     {
         if ($this->logs->removeElement($log)) {
             // set the owning side to null (unless already changed)
-            if ($log->getLexicon() === $this) {
-                $log->setLexicon(null);
+            if ($log->getEntry() === $this) {
+                $log->setEntry(null);
             }
         }
 
diff --git a/src/Entity/Log.php b/src/Entity/Log.php
index 32cc159..6ca7020 100644
--- a/src/Entity/Log.php
+++ b/src/Entity/Log.php
@@ -2,19 +2,24 @@
 
 namespace App\Entity;
 
-use App\Repository\LexiconRepository;
+use App\Repository\LogRepository;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 use Symfony\Component\Serializer\Annotation\Groups;
 
 /**
- * @ORM\Entity(repositoryClass=LexiconRepository::class)
+ * @ORM\Entity(repositoryClass=LogRepository::class)
  * @ORM\HasLifecycleCallbacks
  */
 class Log
 {
-    const CATEGORY_UPDATE = 'Update';
+    const CATEGORY_UPDATE_COMMENT    = 'update_comment';
+    const CATEGORY_UPDATE_DISCUSSION = 'update_discussion';
+
+    const LOG_LIST_CATEGORIES = [
+        self::CATEGORY_UPDATE_COMMENT, self::CATEGORY_UPDATE_DISCUSSION
+    ];
 
     /**
      * @ORM\Id
diff --git a/src/Entity/LoggableTrait.php b/src/Entity/LoggableTrait.php
index cfa4267..a7df36c 100644
--- a/src/Entity/LoggableTrait.php
+++ b/src/Entity/LoggableTrait.php
@@ -8,20 +8,24 @@ trait LoggableTrait
 {
     public function setCommentsBy($comments, User $user)
     {
+        $this->setComments($comments);
         $log = new Log();
+        $log->setCategory(Log::CATEGORY_UPDATE_COMMENT);
         $log->setCreatedBy($user);
         $log->setContent($this->getComments());
-        $this->setComments($comments);
+        $this->addLog($log);
 
         return $this;
     }
 
     public function setDiscussionBy($discussions, User $user)
     {
+        $this->setDiscussion($discussions);
         $log = new Log();
+        $log->setCategory(Log::CATEGORY_UPDATE_DISCUSSION);
         $log->setCreatedBy($user);
-        $log->setContent($this->getDiscussions());
-        $this->setDiscussions($discussions);
+        $log->setContent($this->getDiscussion());
+        $this->addLog($log);
 
         return $this;
     }
diff --git a/src/Entity/Trace.php b/src/Entity/Trace.php
index e866f9d..90f02be 100644
--- a/src/Entity/Trace.php
+++ b/src/Entity/Trace.php
@@ -2,7 +2,7 @@
 
 namespace App\Entity;
 
-use App\Repository\LexiconRepository;
+use App\Repository\TraceRepository;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -10,7 +10,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 use OpenApi\Annotations as OA;
 
 /**
- * @ORM\Entity(repositoryClass=LexiconRepository::class)
+ * @ORM\Entity(repositoryClass=TraceRepository::class)
  * @ORM\HasLifecycleCallbacks
  */
 class Trace
diff --git a/src/Repository/LogRepository.php b/src/Repository/LogRepository.php
new file mode 100644
index 0000000..9b2b564
--- /dev/null
+++ b/src/Repository/LogRepository.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace App\Repository;
+
+use App\Entity\Log;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @extends ServiceEntityRepository<Log>
+ *
+ * @method Log|null find($id, $lockMode = null, $lockVersion = null)
+ * @method Log|null findOneBy(array $criteria, array $orderBy = null)
+ * @method Log[]    findAll()
+ * @method Log[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+class LogRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, Log::class);
+    }
+
+    public function search($filter)
+    {
+        $qb = $this->createQueryBuilder('l');
+
+        if ($filter['date'] ?? null) {
+            $qb->andWhere('DATE(l.createdAt) = DATE(:date)')
+                ->setParameter('date', $filter['date'])
+            ;
+        }
+
+        if ($filter['user'] ?? null) {
+            $qb->andWhere('l.createdBy = :user')
+                ->setParameter('user', $filter['user'])
+            ;
+        }
+
+        if ($filter['lexicon'] ?? null) {
+            $qb->andWhere('l.lexicon = :lexicon')
+                ->setParameter('lexicon', $filter['lexicon'])
+            ;
+        }
+
+        if ($filter['entry'] ?? null) {
+            $qb->andWhere('l.entry = :entry')
+                ->setParameter('entry', $filter['entry'])
+            ;
+        }
+
+        if ($filter['category'] ?? null) {
+            $qb->andWhere('l.category = :category')
+                ->setParameter('category', $filter['category'])
+            ;
+        }
+
+        return $qb->getQuery()->getResult();
+
+    }
+
+    public function add(Log $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->persist($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+    public function remove(Log $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->remove($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+//    /**
+//     * @return Log[] Returns an array of Log objects
+//     */
+//    public function findByExampleField($value): array
+//    {
+//        return $this->createQueryBuilder('e')
+//            ->andWhere('e.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->orderBy('e.id', 'ASC')
+//            ->setMaxResults(10)
+//            ->getQuery()
+//            ->getResult()
+//        ;
+//    }
+
+//    public function findOneBySomeField($value): ?Log
+//    {
+//        return $this->createQueryBuilder('e')
+//            ->andWhere('e.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->getQuery()
+//            ->getOneOrNullResult()
+//        ;
+//    }
+}
diff --git a/src/Repository/TraceRepository.php b/src/Repository/TraceRepository.php
new file mode 100644
index 0000000..fcb0e3b
--- /dev/null
+++ b/src/Repository/TraceRepository.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace App\Repository;
+
+use App\Entity\Trace;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @extends ServiceEntityRepository<Trace>
+ *
+ * @method Trace|null find($id, $lockMode = null, $lockVersion = null)
+ * @method Trace|null findOneBy(array $criteria, array $orderBy = null)
+ * @method Trace[]    findAll()
+ * @method Trace[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+class TraceRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, Trace::class);
+    }
+
+    public function search($filter)
+    {
+        $qb = $this->createQueryBuilder('t');
+
+        if ($filter['date'] ?? null) {
+            $qb->andWhere('DATE(t.createdAt) = DATE(:date)')
+                ->setParameter('date', $filter['date'])
+            ;
+        }
+
+        if ($filter['user'] ?? null) {
+            $qb->andWhere('t.createdBy = :user')
+                ->setParameter('user', $filter['user'])
+            ;
+        }
+
+        if ($filter['action'] ?? null) {
+            $qb->andWhere('t.action = :action')
+                ->setParameter('action', $filter['action'])
+            ;
+        }
+
+        if ($filter['origin'] ?? null) {
+            $qb->andWhere('t.origin = :origin')
+                ->setParameter('origin', $filter['origin'])
+            ;
+        }
+
+        return $qb->getQuery()->getResult();
+
+    }
+
+    public function add(Trace $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->persist($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+    public function remove(Trace $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->remove($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+//    /**
+//     * @return Trace[] Returns an array of Trace objects
+//     */
+//    public function findByExampleField($value): array
+//    {
+//        return $this->createQueryBuilder('e')
+//            ->andWhere('e.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->orderBy('e.id', 'ASC')
+//            ->setMaxResults(10)
+//            ->getQuery()
+//            ->getResult()
+//        ;
+//    }
+
+//    public function findOneBySomeField($value): ?Trace
+//    {
+//        return $this->createQueryBuilder('e')
+//            ->andWhere('e.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->getQuery()
+//            ->getOneOrNullResult()
+//        ;
+//    }
+}
-- 
GitLab