diff --git a/composer.json b/composer.json index b3dd9ee..52f15c5 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "ext-ctype": "*", "ext-iconv": "*", "composer/package-versions-deprecated": "1.11.99.1", + "doctrine/annotations": "^1.12", "doctrine/doctrine-bundle": "^2.3", "doctrine/doctrine-migrations-bundle": "^3.1", "doctrine/orm": "^2.8", diff --git a/composer.lock b/composer.lock index 983ee2b..28526c4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f5dc0da6106cdc7158e5aa765c01596f", + "content-hash": "cbc3e39376e9371be37305d6cd955251", "packages": [ { "name": "composer/package-versions-deprecated", diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php old mode 100644 new mode 100755 index 1eefe68..dead6af --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -2,17 +2,99 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use App\Entity\Poll; +use FOS\RestBundle\Controller\Annotations\Get; +use FOS\RestBundle\Controller\Annotations\Route; -class AdminController extends AbstractController -{ - #[Route('/admin', name: 'admin')] - public function index(): Response - { - return $this->render('admin/index.html.twig', [ - 'controller_name' => 'AdminController', - ]); - } +/** + * Class DefaultController + * @package App\Controller + * @Route("/admin",name="admin_homepage") + */ +class AdminController extends FramadateController { + /** + * @Get(path ="/", + * name = "_get_default") + */ + public function indexAction() { + + return $this->json( [ "message" => "welcome to the framadate admin api, ask /api/v1/doc.json for endpoints" ], + 200 ); + } + + /** + * Delete all expired polls and their children + * @Get( + * path = "/polls/clean/{token}", + * name = "_clean_expired_polls", + * ) + * token is set up in the main env file + */ + public + function cleanExpiredPolls( + string $token + ) { + if ( $this->getParameter( 'admin_token' ) !== $token ) { + return $this->json( [ + 'message' => 'clean routine can NOT be done, your admin token is bad, and you should feel bad.', + ], + 403 ); + } + $em = $this->getDoctrine()->getManager(); + $emPoll = $this->getDoctrine()->getRepository( Poll::class ); + + $queryFind = $em->createQuery( + 'SELECT p + FROM App\Entity\Poll p + WHERE p.expiracyDate < CURRENT_DATE()' + ); + $queryDelete = $em->createQuery( + 'DELETE + FROM App\Entity\Poll p + WHERE p.expiracyDate < CURRENT_DATE()' + ); + + $foundPolls = $queryFind->getResult(); + + $em->flush(); + + + return $this->json( [ + 'message' => 'clean routine has been done, here are the numbers of polls deleted: ' . count( $foundPolls ), + 'data' => [ + 'count' => count( $foundPolls ), + ], + ], + 200 ); + } + + /** + * Delete all expired polls and their children + * @Get( + * path = "/polls/migrate", + * name = "_migrate_framadate", + * ) + * token is set up in the main env file + */ + public + function runMigrationFromOldFramadate( + string $token + ) { + // TODO + // fetch old polls and store their properties in new poll objects + + $foundPolls = []; + $database_name = 'symfony'; + + $em = $this->getDoctrine()->getManager(); + $emPoll = $this->getDoctrine()->getRepository( Poll::class ); + + return $this->json( [ + 'message' => 'migration done for: ' . count( $foundPolls ). ' - this feature is not ready to work YET.', + 'data' => [ + 'count' => count( $foundPolls ), + ], + ], + 200 ); + } } diff --git a/src/Controller/CommentController.php b/src/Controller/CommentController.php new file mode 100644 index 0000000..b2ef502 --- /dev/null +++ b/src/Controller/CommentController.php @@ -0,0 +1,177 @@ +serialize([ + 'message' => 'here are your comments of the poll', + 'data' => $poll->getComments()], 'json'); + + $response = new Response($jsonResponse); + $response->headers->set('Content-Type', 'application/json'); + $response->setStatusCode(200); + + return $response; + + } + + /** + * add a comment on a poll + * @Post( + * path = "/poll/{id}/comment", + * name = "new_comment", + * requirements = {"content"="\w+", "id"="\d+"} + * ) + */ + public + function newCommentAction( + Poll $poll, + Request $request + ) { + if ( ! $poll ) { + return $this->json( [ 'message' => 'poll not found' ], 404 ); + } + $data = $request->getContent(); + + $serializer = SerializerBuilder::create()->build(); + $comment = $serializer->deserialize( $data, 'App\Entity\Comment', 'json' ); + + $em = $this->getDoctrine()->getRepository( Owner::class ); + + $data = json_decode( $data, true ); + if(!isset($data['email'])) { + return $this->json(["message" => "Incorrect JSON in request"], 400); + } + + $foundOwner = $em->findOneByEmail( $data[ 'email' ] ); + // manage existing or new Owner + if ( ! $foundOwner ) { + $foundOwner = new Owner(); + $foundOwner->setPseudo( $data[ 'email' ] ) + ->setEmail( $data[ 'email' ] ) + ->setModifierToken( uniqid( '', true ) ); + } + // anti flood + $seconds_limit_lastpost = 5; + $emComment = $this->getDoctrine()->getRepository( Comment::class ); + $lastCommentOfOwner = $emComment->findBy( [ 'owner' => $foundOwner ], [ 'id' => 'desc' ] ); + + // TODO anti flood by session / IP + + if ( $lastCommentOfOwner ) { + + + // check time of last comment + $now = new DateTime(); + $now = $now->format( 'Y-m-d H:i:s' ); + $date_first = strtotime( $lastCommentOfOwner[ 0 ]->getCreatedAt()->format( 'Y-m-d H:i:s' ) ); + $date_second = strtotime( $now ); + + if ( ( $date_second - $date_first ) < $seconds_limit_lastpost ) { + return $this->json( [ + 'message' => 'anti flood déclenché', + 'details' => 'votre deriner commentaire a été envoyé il y a moins de ' . $seconds_limit_lastpost . ' secondes', + ], + 403 ); + } + + // check similar text content + if ( $lastCommentOfOwner[ 0 ]->getText() == $comment->getText() ) { + return $this->json( [ + 'message' => 'anti flood déclenché', + 'details' => 'votre deriner commentaire a exactement le même contenu que celui ci, il n\'a donc pas été créé', + ], + 403 ); + } + } + $comment->setOwner( $foundOwner ) + ->setCreatedAt( new DateTime() ) + ->setPoll( $poll ); + $foundOwner->addComment( $comment ); + + $em = $this->getDoctrine()->getManager(); + $em->persist( $foundOwner ); + $em->persist( $comment ); + $em->flush(); + + if($poll->getMailOnComment()){ + $this->sendCommentNotificationAction($foundOwner, $comment); + } + + + return $this->json( [ + 'message' => 'you created a comment', + 'data' => [ + 'your_comment' => $comment->display(), + ], + ], + 201 ); + } + + /** + * Erase all comments of a poll + * @Delete( + * path = "/poll/{id}/comments", + * name = "poll_comments_delete", + * requirements = {"accessToken"="\w+", "id"="\d+"} + * ) + * + * @param Poll $poll + * @param $accessToken + * + * @return JsonResponse + */ + public + function deletePollCommentsAction( + Poll $poll, + $accessToken + ) { + if ( $accessToken == $poll->getAdminKey() ) { + $em = $this->getDoctrine()->getManager(); + $length = count( $poll->getComments() ); + $em->remove( $poll->getComments() ); + $em->flush(); + + return $this->json( [ + 'message' => 'boom! les ' . $length . ' commentaires du sondage ont été supprimés', + ] ); + } else { + return $this->json( [ + 'message' => 'le token d\'autorisation est invalide, vous ne pouvez pas modifier ce sondage', + ] ); + } + } + +} diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php old mode 100644 new mode 100755 index 59fb080..67f4455 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -2,17 +2,33 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use App\Entity\Owner; +use App\Entity\Poll; +use App\Repository\PollRepository; +use App\Service\MailService; +use FOS\RestBundle\Controller\Annotations\Get; +use FOS\RestBundle\Controller\Annotations\Route; +use JMS\Serializer\Type\Exception\Exception; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; + +/** + * Class DefaultController + * @package App\Controller + * @Route("/",name="home_") + */ +class DefaultController extends FramadateController { + + /** + * @Get(path ="/", + * name = "sweet_home") + */ + public function indexAction() { + + $polls = $this->getDoctrine()->getRepository( Poll::class )->findAll(); + + return $this->render( 'pages/home.html.twig',[ ]); + } + -class DefaultController extends AbstractController -{ - #[Route('/', name: 'default')] - public function index(): Response - { - return $this->render('pages/home.html.twig', [ - 'controller_name' => 'DefaultController', - ]); - } } diff --git a/src/Controller/FramadateController.php b/src/Controller/FramadateController.php old mode 100755 new mode 100644 index 0a6d82e..0e66487 --- a/src/Controller/FramadateController.php +++ b/src/Controller/FramadateController.php @@ -7,8 +7,6 @@ use App\Entity\Poll; use JMS\Serializer\Type\Exception\Exception; use Swift_Message; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; /** * sending emails controller @@ -20,14 +18,16 @@ class FramadateController extends AbstractController { private $mail_service; -// public function __construct( \Swift_Mailer $mailer ) { -// $this->mail_service = $mailer; -// } + public function __construct( \Swift_Mailer $mailer ) { + $this->mail_service = $mailer; + } /** * generic way to send email with html template * * @param $config + * + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ public function sendMailWithVars( $config ) { @@ -69,7 +69,6 @@ class FramadateController extends AbstractController { $numSent = $this->mail_service->send( $message ); $this->numSent = $numSent; - return 1; } @@ -81,6 +80,7 @@ class FramadateController extends AbstractController { * * @return int|void * @throws Exception + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ public function sendOwnerPollsAction( Owner $owner ) { @@ -98,6 +98,7 @@ class FramadateController extends AbstractController { * @param Owner $foundOwner * @param Poll|null $poll * + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ public function sendCreationMailAction( Owner $foundOwner, Poll $poll = null ) { @@ -119,6 +120,7 @@ class FramadateController extends AbstractController { * @param $comment * * @return int + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ public function sendCommentNotificationAction( Owner $owner, $comment ) { @@ -139,6 +141,7 @@ class FramadateController extends AbstractController { * @param $stackOfVotes * * @return int + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ public function sendVoteNotificationAction( Owner $owner, $stackOfVotes ) { diff --git a/src/Controller/MigrationController.php b/src/Controller/MigrationController.php old mode 100644 new mode 100755 index 2d8dd5e..62eaea8 --- a/src/Controller/MigrationController.php +++ b/src/Controller/MigrationController.php @@ -2,25 +2,31 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use App\Entity\Poll; +use FOS\RestBundle\Controller\Annotations\Get; +use FOS\RestBundle\Controller\Annotations\Route; + +/** + * Class DefaultController + * @package App\Controller + * @Route("/migration-from-v1",name="admin_homepage") + */ +class MigrationController extends FramadateController { + /** + * @Get(path ="/{unique_key}", + * name = "_migrate_from_v1") + */ + public function indexAction() { + // get env vars + // check uniq key is good + // fetch old Database + // gather objects + // create new polls + // success + // failure notice + + return $this->json( [ "message" => "welcome to the framadate migration endpoint, it has yet to be done" ], + 200 ); + } -class MigrationController extends AbstractController -{ - #[Route('/migration', name: 'migration')] - public function index(): Response - { - // TODO - // get env vars - // check uniq key is good - // fetch old Database - // gather objects - // create new polls - // success - // failure notice - return $this->render('migration/index.html.twig', [ - 'controller_name' => 'MigrationController', - ]); - } } diff --git a/src/Controller/OwnerController.php b/src/Controller/OwnerController.php old mode 100644 new mode 100755 index 9dc80f9..4423221 --- a/src/Controller/OwnerController.php +++ b/src/Controller/OwnerController.php @@ -2,17 +2,84 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use App\Entity\Owner; +use FOS\RestBundle\Controller\Annotations\Get; +use FOS\RestBundle\Controller\Annotations\Route; +use JMS\Serializer\Type\Exception\Exception; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; + +/** + * Class DefaultController + * @package App\Controller + * @Route("/user",name="user_homepage") + */ +class OwnerController extends FramadateController { + /** + * @Get(path ="/", + * name = "get_default") + */ + public function indexAction() { + + return $this->json( [ "message" => "welcome to the framadate user api, ask /api/v1/doc.json for endpoints" ], + 200 ); + } + + + /** + * Send a mail with all the data to one user + * @Get( + * path = "/{email}/polls/send-by-email", + * name = "_polls_send_by_email" + * ) + * + * @param string $email + * + * @return JsonResponse + */ + public function sendPollsToUserAction( string $email ) { + $repository = $this->getDoctrine()->getRepository( Owner::class ); + + + // find user by email + $owner = $repository->findOneByEmail( $email ); + + if ( $owner ) { + $templateVars = [ + 'owner' => $owner, + 'polls' => $owner->getPolls(), + 'title' => 'Mes sondages - ' . $owner->getEmail(), + ]; + + // send email + $mailSent = 0; + try { + $mailSent = $this->sendOwnerPollsAction( $owner ); + } catch ( Exception $e ) { + } catch ( TransportExceptionInterface $e ) { + } + + if ( $mailSent ) { + return $this->json( [ + 'message' => 'mail succefully sent to user ' . $owner->getEmail(), + 'data' => '', + ], + 200 ); + } + + return $this->json( [ + 'message' => 'no sucess sending email ' . $owner->getEmail(), + 'data' => '', + ], + 400 ); + } + + return $this->json( [ + 'message' => 'no user found for email ' . $email, + 'data' => '', + ], + 400 ); + + } -class OwnerController extends AbstractController -{ - #[Route('/owner', name: 'owner')] - public function index(): Response - { - return $this->render('owner/index.html.twig', [ - 'controller_name' => 'OwnerController', - ]); - } } diff --git a/src/Controller/PollController.php b/src/Controller/PollController.php new file mode 100644 index 0000000..ab099e8 --- /dev/null +++ b/src/Controller/PollController.php @@ -0,0 +1,104 @@ +render('poll/index.html.twig', [ + 'polls' => count($pollRepository->findAll()), + ]); + } + + /** + * @Route("/new", name="poll_new", methods={"GET","POST"}) + */ + public function new(Request $request): Response + { + $poll = new Poll(); + $form = $this->createForm(PollType::class, $poll); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($poll); + $entityManager->flush(); + + return $this->redirectToRoute('poll_index'); + } + + return $this->render('poll/new.html.twig', [ + 'poll' => $poll, + 'form' => $form->createView(), + ]); + } + + /** + * on cherche un sondage par son url personnalisée + * @Route("/{id}", name="poll_show", methods={"GET"}) + */ + public function show($id): Response + { + $repository = $this->getDoctrine()->getRepository(Poll::class); + $foundPoll = $repository->findOneByCustomUrl($id); + if(!$foundPoll){ + return $this->json([ + 'message' => $id.' : not found' + ], 404); + } + + return $this->render('poll/show.html.twig', [ + 'poll' => $foundPoll, + ]); + } + + /** + * @Route("/{id}/edit", name="poll_edit", methods={"GET","POST"}) + */ + public function edit(Request $request, Poll $poll): Response + { + $form = $this->createForm(PollType::class, $poll); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->flush(); + + return $this->redirectToRoute('poll_index'); + } + + return $this->render('poll/edit.html.twig', [ + 'poll' => $poll, + 'form' => $form->createView(), + ]); + } + + /** + * @Route("/{id}", name="poll_delete", methods={"DELETE"}) + */ + public function delete(Request $request, Poll $poll): Response + { + if ($this->isCsrfTokenValid('delete'.$poll->getId(), $request->request->get('_token'))) { + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->remove($poll); + $entityManager->flush(); + } + + return $this->redirectToRoute('poll_index'); + } +} diff --git a/src/Controller/VoteController.php b/src/Controller/VoteController.php index 2e5ad18..9d83fb3 100644 --- a/src/Controller/VoteController.php +++ b/src/Controller/VoteController.php @@ -2,17 +2,215 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use App\Entity\Choice; +use App\Entity\Owner; +use App\Entity\Poll; +use App\Entity\StackOfVotes; +use App\Entity\Vote; +use FOS\RestBundle\Controller\Annotations\Delete; +use FOS\RestBundle\Controller\Annotations\Patch; +use FOS\RestBundle\Controller\Annotations\Post; +use FOS\RestBundle\Controller\Annotations\Route; +use JMS\Serializer\SerializerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -class VoteController extends AbstractController -{ - #[Route('/vote', name: 'vote')] - public function index(): Response - { - return $this->render('vote/index.html.twig', [ - 'controller_name' => 'VoteController', - ]); - } +/** + * Class DefaultController + * @package App\Controller + * @Route("/api/v1",name="api_") + */ +class VoteController extends FramadateController { + + /** + * add a vote stack on a poll + * @Post( + * path = "/poll/{id}/answer", + * name = "new_vote_stack", + * requirements = {"content"="\w+", "poll_id"="\d+"} + * ) + * @param SerializerInterface $serializer + * @param Poll $poll + * @param Request $request + * + * @return JsonResponse|Response + */ + public function newVoteStackAction( + SerializerInterface $serializer, + Poll $poll, + Request $request + ) { + if ( ! $poll ) { + return $this->json( [ 'message' => 'poll not found' ], 404 ); + } + + $em = $this->getDoctrine()->getManager(); + $data = $request->getContent(); + $data = json_decode( $data, true ); + + + $emOwner = $this->getDoctrine()->getRepository( Owner::class ); + $emChoice = $this->getDoctrine()->getRepository( Choice::class ); + $existingOwner = false; + $foundOwner = $emOwner->findOneByEmail( trim( $data[ 'email' ] ) ); + // manage existing or new Owner + if ( ! $foundOwner ) { + $foundOwner = new Owner(); + $foundOwner + ->setEmail( $data[ 'email' ] ) + ->setPseudo( $data[ 'pseudo' ] ); + } else { + $existingOwner = true; + } + // TODO anti flood + $foundOwner + ->setModifierToken( $poll->generateAdminKey() ); + $stack = new StackOfVotes(); + $stack + ->setOwner( $foundOwner ) + ->setPseudo( $data[ 'pseudo' ] ) + ->setPoll( $poll ); + foreach ( $data[ 'votes' ] as $voteInfo ) { + + if ( ! isset( $voteInfo[ 'value' ] ) ) { + continue; + } + $allowedValuesToAnswer = [ 'yes', 'maybe', 'no' ]; + + if ( ! in_array( $voteInfo[ 'value' ], $allowedValuesToAnswer ) ) { + return $this->json( [ + 'message' => 'answer ' . $voteInfo[ 'value' ] . ' is not allowed. should be yes, maybe, or no.', + 'vote_stack' => $stack, + ], + 404 ); + } + $vote = new Vote(); + $foundChoice = $emChoice->find( $voteInfo[ 'choice_id' ] ); + if ( ! $foundChoice ) { + return $this->json( [ + 'message' => 'choice ' . $voteInfo[ 'choice_id' ] . ' was not found', + 'vote_stack' => $stack, + ], + 404 ); + } + $vote->setPoll( $poll ) + ->setChoice( $foundChoice ) + ->setValue( $voteInfo[ 'value' ] ); + $vote->setPoll( $poll ); + $stack->addVote( $vote ); + $poll->addVote( $vote ); + $em->persist( $vote ); + $em->persist( $foundChoice ); + } + + // find poll from choices + $poll->addStackOfVote( $stack ); + $em->persist( $stack ); + $em->persist( $poll ); + $em->flush(); + $precision = ''; + if ( $existingOwner ) { + $precision = ' from an existing owner : ' . $foundOwner->getEmail(); + } + $stacks = $poll->getStacksOfVotes(); + + if($poll->getMailOnVote()){ + $this->sendVoteNotificationAction($stack->getOwner(), $stack); + } + + $returnedVoteStack = $stack; + + $jsonResponse = $serializer->serialize($returnedVoteStack, 'json'); + + $response = new Response($jsonResponse); + $response->headers->set('Content-Type', 'application/json'); + $response->setStatusCode(200); + + return $response; + + } + + /** + * update vote stack + * @Patch( + * path = "/vote-stack/{id}/token/{modifierToken}", + * name = "update_vote_stack", + * requirements = { "id"="\d+"} + * ) + * @param SerializerInterface $serializer + * @param StackOfVotes $id + * @param $modifierToken + * @param Request $request + * + * @return JsonResponse|Response + */ + public + function updateVoteStackAction( + SerializerInterface $serializer, + StackOfVotes $id, + $modifierToken, + Request $request + ) { + $voteStack = $id; + if ( ! $voteStack ) { + return $this->json( [ 'message' => 'vote stack not found' ], 404 ); + } + $poll = $voteStack->getPoll(); + + // if only self users are allowed to modify a vote, check it + if ( ! $modifierToken || $voteStack->getOwner()->getModifierToken() !== $modifierToken ) { + return $this->json( [ 'message' => 'your token does not allow you to modify this vote ' ], + 403 ); + } + // everything is ok, we can update all the votes of the vote stack + //TODO + // match votes and choices + // update answers + // save evrything + + + $jsonResponse = $serializer->serialize([ + 'message' => 'ok', + 'modifier_token' => $voteStack->getOwner()->getModifierToken(), + 'vote_stack' => $voteStack, + ], 'json'); + + $response = new Response($jsonResponse); + $response->headers->set('Content-Type', 'application/json'); + $response->setStatusCode(200); + + return $response; + + + } + + /** + * @Delete( + * path = "/poll/{id}/votes/{accessToken}", + * name = "poll_votes_delete", + * requirements = {"accessToken"="\w+", "poll_id"="\d+"} + * ) + * @return JsonResponse + */ + public + function deletePollVotesAction( + Poll $poll, + $accessToken + ) { + if ( $accessToken == $poll->getAdminKey() ) { + $em = $this->getDoctrine()->getManager(); + $length = count( $poll->getVotes() ); + $em->remove( $poll->getVotes() ); + $em->flush(); + + return $this->json( [ + 'message' => 'boom! les ' . $length . ' votes du sondage ont été supprimés', + ],200 ); + } else { + return $this->json( [ + 'message' => 'le token d\'autorisation est invalide, vous ne pouvez pas modifier ce sondage' + ],403 ); + } + } } diff --git a/src/Controller/api/PollController.php b/src/Controller/api/PollController.php new file mode 100644 index 0000000..8409715 --- /dev/null +++ b/src/Controller/api/PollController.php @@ -0,0 +1,474 @@ +getDoctrine()->getRepository( Poll::class ); + $data = $repository->findAll(); + + + return $this->json( [ + 'message' => 'here are your polls', + 'poll' => count( $data ), + ] ); + } + + /** + * @param $id + * message when the poll is not found + * @return JsonResponse + */ + public function notFoundPoll($id){ + return $this->json( [ + 'message' => $id . ' : poll not found', + ], + 404 ); + } + /** + * get a poll config by its custom URL, we do not want polls to be reachable by their numeric id + * @Get( + * path = "/{id}", + * name = "get_poll", + * requirements = {"id"="\w+"} + * ) + * + * @param SerializerInterface $serializer + * @param Request $request + * + * @return JsonResponse|Response + */ + public function getPollConfig( + SerializerInterface $serializer, + $id, + Request $request + ) { + $repository = $this->getDoctrine()->getRepository( Poll::class ); + $poll = $repository->findOneByCustomUrl( $id ); + + if ( ! $poll ) { + return $this->notFoundPoll($id); + } + + $comments = $poll->getComments(); + $pass = $poll->getPassword(); + + $returnedPoll = [ + 'message' => 'your poll config for ' . $poll->getTitle(), + 'password_protected' => $pass ? 'yes' : 'no', + // TODO do not render sub objects of owner, it returns too many things + 'poll' => $poll, + 'stacks_count' => count( $poll->getStacksOfVotes() ), + 'stacks' => $poll->getStacksOfVotes(), + 'choices_count' => $poll->computeAnswers(), + 'choices' => $poll->getChoices(), + 'comments' => $comments, + 'comments_count' => count( $comments ), + ]; + + /** + * password protected content + */ + if ( $pass ) { + // no password possibly given by this route + return $this->json( [ + 'message' => 'this is protected by a password,but you did not provide the encoded password parameter, and you should feel bad. ' , + ], + 403 ); + + } else { + // free access to poll + return $this->returnPollData( $poll, $serializer ); + } + + } + + /** + * get a poll config by its custom URL, we do not want polls to be reachable by their numeric id + * @Get( + * path = "/{id}/pass/{md5}", + * name = "get_protected_poll", + * requirements = {"id"="\w+"} + * ) + * + * @param SerializerInterface $serializer + * @param Request $request + * + * @return JsonResponse|Response + */ + function getProtectedPoll($id,$md5, SerializerInterface $serializer){ + $repository = $this->getDoctrine()->getRepository( Poll::class ); + $poll = $repository->findOneByCustomUrl( $id ); + + if ( ! $poll ) { + return $this->notFoundPoll($id); + } + + if ( $poll->getPassword() === $md5 ) { + // good matching pass + return $this->returnPollData( $poll, $serializer ); + } else { + // wrong pass + return $this->json( [ + 'message' => 'this is protected by a password, your password "' . $md5 . '" is wrong, and you should feel bad', + 'md5' => md5($md5), + 'data' => null, + ], + 403 ); + } + + + } + + function returnPollData( $poll, $serializer ) { + $jsonResponse = $serializer->serialize( $poll, 'json' ); + + $response = new Response( $jsonResponse ); + $response->headers->set( 'Content-Type', 'application/json' ); + $response->setStatusCode( 200 ); + + return $response; + } + + /** + * @Put( + * path = "/{id}/{token}", + * name = "update_poll", + * requirements = {"content"="\w+", "poll_id"="\d+"} + * ) + */ + public function updatePollConfig( + Poll $poll, + string $token, + Request $request + ) { + if ( $poll->getAdminKey() !== $token ) { + return $this->json( [ + 'message' => 'you are NOT allowed to update the poll ' . $poll->getTitle(), + ], + 403 ); + } + + // TODO check validity of request + // update only if we have the admin key + $em = $this->getDoctrine()->getManager(); + $em->persist( $poll ); + $em->flush(); + + return $this->json( [ + 'message' => 'you updated the poll ' . $poll->getTitle(), + ], + 200 ); + } + + /** + * @Post( + * path = "/", + * name = "new_poll", + * requirements = {"creator"="\w+"} + * ) + * @param Request $request + * + * @return JsonResponse + */ + public function newPollAction( Request $request ) { + + $data = $request->getContent(); + + $serializer = SerializerBuilder::create()->build(); + try { + $newpoll = $serializer->deserialize( $data, 'App\Entity\Poll', 'json' ); + } catch ( RuntimeException $e ) { + return $this->json( [ "message" => "Incorrect JSON in request" ], 400 ); + } + $newpoll + ->setAdminKey( $newpoll->generateAdminKey() ) + ->setCreationDate( new DateTime() ) + ->setModificationPolicy( 'nobody' ); + $timeStamp = time() + ( 3600 * 24 * 90 ); // 90 days by default + $newpoll->setExpiracyDate( ( new DateTime() )->setTimestamp( $timeStamp ), + new DateTimeZone( 'Europe/Paris' ) ); + $data = json_decode( $data, true ); + $em = $this->getDoctrine()->getRepository( Owner::class ); + $foundOwner = $em->findOneBy( [ 'email' => $data[ 'owner' ][ 'email' ] ] ); + + + $userWasFound = false; + if ( ! $foundOwner ) { + //create a new owner + $owner = new Owner(); + + $owner->setPseudo( $data[ 'owner' ][ 'pseudo' ] ); + $owner->setEmail( $data[ 'owner' ][ 'email' ] ); + $foundOwner = $owner; + } else { + $userWasFound = true; + } + // link the owner and the poll + $newpoll->setOwner( $foundOwner ); + $foundOwner->addPoll( $newpoll ); + + + $em = $this->getDoctrine()->getManager(); + $em->persist( $newpoll ); + $em->persist( $foundOwner ); + + // emails + $newpoll->setMailOnComment( true ); + $newpoll->setMailOnVote( true ); + $newpoll->setHideResults( false ); + // possible answers + $newpoll->setAllowedAnswers( [ 'yes' ] ); + if ( $data[ 'voteChoices' ] ) { + switch ( $data[ 'voteChoices' ] ) { + case "only_yes": + default: + + break; + } + } + // setup the password, converting the raw with md5 hash + if ( $data[ 'password' ] ) { + $newpoll->setPassword( $data[ 'password' ] ); + } + // manage choices + // text kind of answers, dates are below + if ( $data[ 'pollType' ] == 'classic' ) { + $choices = $data[ 'dateList' ]; + foreach ( $choices as $c ) { + $newChoice = new Choice(); + $newChoice + ->setPoll( $newpoll ) +// ->setUrl( $c[ 'url' ] ) + ->setName( $c[ 'literal' ] ); + $em->persist( $newChoice ); + // TODO add also choices for each time range in a day + } + } elseif ( $data[ 'pollType' ] == 'dates' ) { + if ( $data[ 'allowSeveralHours' ] == true ) { + // different hours spans + $choices = $data[ 'dateList' ]; + } else { +//TODO (Sébastien) I assume this shouldn't be empty ? +// all days have the same hour spans + } + + + } + + $em->persist( $newpoll ); + $em->flush(); + $precision = ''; + if ( $userWasFound ) { + $precision = 'from an existing user : ' . $foundOwner->getEmail(); + } + + $this->sendCreationMailAction( $foundOwner, $newpoll ); + + return $this->json( [ + 'message' => 'you created a poll ' . $precision, + 'poll' => $newpoll, + 'password_protected' => is_string( $newpoll->getPassword() ), + 'admin_key' => $newpoll->getAdminKey(), + 'owner_modifier_token' => $foundOwner->getModifierToken(), + + ], + 201 ); + + } + + /** + * @Get( + * path = "/mail/test-mail-poll/{emailChoice}", + * name = "test-mail-poll", + * ) + * + * send the creation mail to owner + * + * @param Owner $admin_user + * @param Poll $poll + * @param Swift_Mailer $mailer + * + * @return int + * not that the email tktest_commentateur@tktest.com does not really exist + */ +// public function sendCreationMailAction( Owner $admin_user, Poll $poll, \Swift_Mailer $mailer) { + public function testSendCreationMailAction( + $emailChoice = 'tktest_commentateur@tktest.com' + ) { + $em = $this->getDoctrine()->getRepository( Owner::class ); + $foundOwner = $em->findOneByEmail( $emailChoice ); + if ( $foundOwner ) { + $poll = $foundOwner->getPolls()[ 0 ]; + $comment = $foundOwner->getComments()[ 0 ]; + + $sent = $this->sendOwnerPollsAction( $foundOwner, $poll ); + if ( $sent ) { + return $this->json( [ "message" => "test email sent to " . $foundOwner->getEmail() . "!" ], 200 ); + } + } + + return $this->json( [ "message" => "user with this email was not found" ], 400 ); + + } + + + /** + * @Delete( + * path = "/{id}", + * name = "poll_delete", + * requirements = {"accessToken"="\w+", "poll_id"="\d+"} + * ) + * @param Poll $poll + * @param $accessToken + * + * @return JsonResponse + */ + public + function deletePollAction( + Poll $poll, + $accessToken + ) { + + if ( $accessToken == $poll->getAdminKey() ) { + $em = $this->getDoctrine()->getManager(); + $em->remove( $poll ); + $em->flush(); + + return $this->json( [ + 'message' => 'boom! le sondage et ses objets assocités a été supprimé', + ] ); + } else { + return $this->json( [ + 'message' => 'le token d\'autorisation est invalide, vous ne pouvez pas modifier ce sondage', + ] ); + } + + } + + /** + * Checks if a slug is already taken by a poll + * @Get( + * path = "/slug/{slug}", + * name = "check_slug_is_unique", + * ) + */ + public function checkSlugIsUniqueAction( string $slug ) { + $emPoll = $this->getDoctrine()->getRepository( Poll::class ); + $found = $emPoll->findOneByCustomUrl( $slug ); + $elaborated_message_version = false; + + if ( $found ) { + if ( ! $elaborated_message_version ) { + return $this->json( null, + 204 ); + } + + // we should use an other slug + return $this->json( [ + 'message' => ' NO, this slug is already taken on this Framadate instance ', + 'data' => [ + 'slug' => $slug, + ], + ], + 204 ); + } + if ( ! $elaborated_message_version ) { + return $this->json( null, + 404 ); + } + + return $this->json( [ + 'message' => ' yes this slug is available on this Framadate instance ', + 'data' => [ + 'slug' => $slug, + ], + ], + 404 ); + + } + + /** + * Get Admin poll config + * @Get( + * path = "/admin/{token}", + * name = "get_admin_config", + * ) + * + * @param SerializerInterface $serializer + * @param $token + * + * @return JsonResponse|Response + */ + public function getAdministrationConfig( SerializerInterface $serializer, $token ) { + $emPoll = $this->getDoctrine()->getRepository( Poll::class ); + $pollFound = $emPoll->findOneByAdminKey( $token ); + + if ( $pollFound ) { + + $poll = $pollFound; + $comments = $poll->getComments(); + $stacks = $poll->getStacksOfVotes(); + + $returnedPoll = [ + 'message' => 'your poll config', + 'poll' => $poll, + 'stacks_count' => count( $stacks ), + 'stacks' => $stacks, + 'choices_count' => $poll->computeAnswers(), + 'choices' => $poll->getChoices(), + 'comments' => $comments, + 'comments_count' => count( $comments ), + 'token' => $token, + ]; + + $jsonResponse = $serializer->serialize( $returnedPoll, 'json' ); + + $response = new Response( $jsonResponse ); + $response->headers->set( 'Content-Type', 'application/json' ); + $response->setStatusCode( 200 ); + + return $response; + } + + return $this->json( [ + 'message' => 'You are not allowed to do anything with this token', + 'data' => [ + 'token' => $token, + ], + ], + 403 ); + + } + +} diff --git a/templates/split/header.html.twig b/templates/split/header.html.twig index e134ad9..6bb36dc 100644 --- a/templates/split/header.html.twig +++ b/templates/split/header.html.twig @@ -8,8 +8,8 @@
  • - logo - {{ WEBSITE_NAME }} +{# logo#} +{# {{ WEBSITE_NAME }}#}
  • diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..5993723 --- /dev/null +++ b/update.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# script d'update de framadate api, fait pour fonctionner avec le sous module git funky framadate + + +# bash colors +cecho() { + local code="\033[" + case "$1" in + black | bk) color="${code}0;30m";; + red | r) color="${code}1;31m";; + green | g) color="${code}1;32m";; + yellow | y) color="${code}1;33m";; + blue | b) color="${code}1;34m";; + purple | p) color="${code}1;35m";; + cyan | c) color="${code}1;36m";; + gray | gr) color="${code}0;37m";; + *) local text="$1" + esac + [ -z "$text" ] && local text="$color$2${code}0m" + echo -e "$text" +} + +cecho g "######################" +cecho g " time to update the framadate setup" +cecho g "######################" + +COMMAND_BUILD="build:prod:demobliss" +# git reset --hard +git pull origin master + +composer install +php bin/console doctrine:schema:update --force + +cecho g "######################" +cecho g " update the funky frontend submodule " +cecho g "############################################" +cecho g " verification des besoins de l'application " +cecho g "############################################" +cecho g " yarn, git, php, node et une base de données" +cecho g "############################################${reset}" +# True if $1 is an executable in $PATH +# Works in both {ba,z}sh +function is_bin_in_path { + if [[ -n $ZSH_VERSION ]]; then + builtin whence -p "$1" &> /dev/null + else # bash: + builtin type -P "$1" &> /dev/null + fi +} + +if [ ! type yarn &> /dev/null ]; then + cecho red " ❌ la commande yarn est introuvable" + exit 1 +fi +cecho g " ✅ yarn" +if [ ! type node &> /dev/null ]; then + cecho red " ❌ la commande node est introuvable" + exit 1 +fi +cecho g " ✅ node" +if [ ! type git &> /dev/null ]; then + cecho g " ❌ la commande git est introuvable" + exit 1 +fi +cecho g " ✅ git" + +if [ ! -f .env ]; then + cecho g " ❌ fichier d'environnement de symfony /.env" + exit 1 +fi +cecho g " ✅ fichier d'environnement de symfony /.env ou /.env.local" + +if [ ! -f bin/console ]; then + cecho g " ❌ fichier console de symfony" + exit 1 +fi +cecho g " ✅ fichier console de symfony /bin/console " + + +if [ ! -d "funky-framadate-front" ]; then + # initiate sub directory for funky front + git submodule add -f https://framagit.org/framasoft/framadate/funky-framadate-front + git submodule init +fi + +git config --global diff.submodule log +git submodule update +cecho g "######################" +cecho g " check dependencies of the frontend with yarn " +cecho g "######################" +cd funky-framadate-front +cecho g "######################" +cecho g " debug info : version of programs" +cecho g " " +cecho b " git current branch " +git show-branch +cecho b " node version " +node -v +cecho b " yarn version " +yarn -v +cecho g " " +cecho b "##############################################" +cecho b " update of the funky part, clean of local repo" +cecho g " " +git reset --hard +git remote -v +git fetch +git pull +yarn +cecho g "######################" +cecho g " building the frontend, should take around 20 seconds " +cecho g " " +cecho y " npm run $COMMAND_BUILD " +cecho g " " +cecho y " start: $(date) " +cecho g "######################" +npm run $COMMAND_BUILD + +cecho g "######################" +cecho g " copying built files in the public folder of the symfony project " +cecho g "######################" +cd ../public +rm ./*.js +rm ./*.css +cd ../funky-framadate-front +cp -r dist/framadate/* ../public/ +COUNT_FILES=$(ls -larth ../public |wc -l) +cd .. +cecho b " $COUNT_FILES fichiers de build copiés dans /public" +cecho g "##################################################################" +cecho b " renaming unieque name of JS chunks to common names for templates " +cd public + +mv runtime* runtime.js +mv main* main.js +mv polyfills-es5* es5-polyfills.js +mv polyfills* other-polyfills.js +mv scripts* scripts.js +mv styles* styles.css +cecho b " finished at ------- $(date) ------- " +cecho g "##################################################################" +cecho g " " +cecho b " done. you can now see your homepage updated " +cecho g " " +cecho g "##################################################################"