Browse Source

implement user stars feature

main
ghost 1 year ago
parent
commit
0a218cfd3a
  1. 105
      src/Controller/UserController.php
  2. 65
      src/Entity/UserStar.php
  3. 52
      src/Repository/UserStarRepository.php
  4. 68
      src/Service/UserService.php
  5. 24
      templates/default/user/info.html.twig

105
src/Controller/UserController.php

@ -3,6 +3,8 @@
namespace App\Controller; namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -56,7 +58,7 @@ class UserController extends AbstractController
continue; continue;
} }
$activityUser = $userService->get( $activityUser = $userService->getUser(
$activity->getUserId() $activity->getUserId()
); );
@ -168,7 +170,7 @@ class UserController extends AbstractController
} }
#[Route( #[Route(
'/{_locale}/user/{id}', '/{_locale}/user/{userId}',
name: 'user_info', name: 'user_info',
defaults: [ defaults: [
'_locale' => '%app.locale%' '_locale' => '%app.locale%'
@ -178,12 +180,25 @@ class UserController extends AbstractController
], ],
)] )]
public function info( public function info(
int $id,
Request $request, Request $request,
TranslatorInterface $translator,
UserService $userService): Response UserService $userService): Response
{ {
// Init user // Init user
if (!$user = $userService->get($id)) $user = $userService->init(
$request->getClientIp()
);
if (!$user->isStatus())
{
// @TODO
throw new \Exception(
$translator->trans('Access denied')
);
}
// Init target user
if (!$userTarget = $userService->getUser($request->get('userId')))
{ {
throw $this->createNotFoundException(); throw $this->createNotFoundException();
} }
@ -193,19 +208,83 @@ class UserController extends AbstractController
'default/user/info.html.twig', 'default/user/info.html.twig',
[ [
'user' => [ 'user' => [
'id' => $user->getId(), 'id' => $userTarget->getId(),
'address' => $request->getClientIp() == $user->getAddress() ? $user->getAddress() : false, 'address' => $request->getClientIp() == $userTarget->getAddress() ? $userTarget->getAddress() : false,
'moderator' => $user->isModerator(), 'moderator' => $userTarget->isModerator(),
'approved' => $user->isApproved(), 'approved' => $userTarget->isApproved(),
'status' => $user->isStatus(), 'status' => $userTarget->isStatus(),
'locale' => $user->getLocale(), 'locale' => $userTarget->getLocale(),
'locales' => $user->getLocales(), 'locales' => $userTarget->getLocales(),
'added' => $user->getAdded(), 'added' => $userTarget->getAdded(),
'identicon' => $userService->identicon( 'identicon' => $userService->identicon(
$user->getAddress(), $userTarget->getAddress(),
48 48
), ),
'star' =>
[
'exist' => (bool) $userService->findUserStar(
$user->getId(),
$userTarget->getId()
),
'total' => $userService->findUserStarsTotalByUserIdTarget(
$userTarget->getId()
)
],
]
] ]
);
}
#[Route(
'/{_locale}/user/star/toggle/{userId}',
name: 'user_star_toggle',
requirements:
[
'userId' => '\d+',
],
methods:
[
'GET'
]
)]
public function toggleStar(
Request $request,
TranslatorInterface $translator,
UserService $userService
): Response
{
// Init user
$user = $userService->init(
$request->getClientIp()
);
if (!$user->isStatus())
{
// @TODO
throw new \Exception(
$translator->trans('Access denied')
);
}
// Init target user
if (!$userTarget = $userService->getUser($request->get('userId')))
{
throw $this->createNotFoundException();
}
// Update
$userService->toggleUserStar(
$user->getId(),
$userTarget->getId(),
time()
);
// Redirect to info page created
return $this->redirectToRoute(
'user_info',
[
'_locale' => $request->get('_locale'),
'userId' => $userTarget->getId()
] ]
); );
} }

65
src/Entity/UserStar.php

@ -0,0 +1,65 @@
<?php
namespace App\Entity;
use App\Repository\UserStarRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserStarRepository::class)]
class UserStar
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?int $userId = null;
#[ORM\Column]
private ?int $userIdTarget = null;
#[ORM\Column]
private ?int $added = null;
public function getId(): ?int
{
return $this->id;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function setUserId(int $userId): static
{
$this->userId = $userId;
return $this;
}
public function getUserIdTarget(): ?int
{
return $this->userIdTarget;
}
public function setUserIdTarget(int $userIdTarget): static
{
$this->userIdTarget = $userIdTarget;
return $this;
}
public function getAdded(): ?int
{
return $this->added;
}
public function setAdded(int $added): static
{
$this->added = $added;
return $this;
}
}

52
src/Repository/UserStarRepository.php

@ -0,0 +1,52 @@
<?php
namespace App\Repository;
use App\Entity\UserStar;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<UserStar>
*
* @method UserStar|null find($id, $lockMode = null, $lockVersion = null)
* @method UserStar|null findOneBy(array $criteria, array $orderBy = null)
* @method UserStar[] findAll()
* @method UserStar[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserStarRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, UserStar::class);
}
public function findUserStar(
int $userId,
int $userIdTarget
): ?UserStar
{
return $this->createQueryBuilder('us')
->where('us.userId = :userId')
->andWhere('us.userIdTarget = :userIdTarget')
->setParameter('userId', $userId)
->setParameter('userIdTarget', $userIdTarget)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
public function findUserStarsTotalByUserIdTarget(
int $userIdTarget
): int
{
return $this->createQueryBuilder('us')
->select('count(us.userId)')
->where('us.userIdTarget = :userIdTarget')
->setParameter('userIdTarget', $userIdTarget)
->getQuery()
->getSingleScalarResult()
;
}
}

68
src/Service/UserService.php

@ -3,6 +3,7 @@
namespace App\Service; namespace App\Service;
use App\Entity\User; use App\Entity\User;
use App\Entity\UserStar;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -10,24 +11,24 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class UserService class UserService
{ {
private EntityManagerInterface $entityManager; private EntityManagerInterface $entityManagerInterface;
private UserRepository $userRepository;
private ParameterBagInterface $parameterBagInterface; private ParameterBagInterface $parameterBagInterface;
public function __construct( public function __construct(
EntityManagerInterface $entityManager, EntityManagerInterface $entityManagerInterface,
ParameterBagInterface $parameterBagInterface ParameterBagInterface $parameterBagInterface
) )
{ {
$this->entityManager = $entityManager; $this->entityManagerInterface = $entityManagerInterface;
$this->userRepository = $entityManager->getRepository(User::class);
$this->parameterBagInterface = $parameterBagInterface; $this->parameterBagInterface = $parameterBagInterface;
} }
public function init(string $address): User public function init(string $address): User
{ {
// Return existing user // Return existing user
if ($result = $this->userRepository->findOneByAddressField($address)) if ($result = $this->entityManagerInterface
->getRepository(User::class)
->findOneByAddressField($address))
{ {
return $result; return $result;
} }
@ -61,9 +62,11 @@ class UserService
return $user; return $user;
} }
public function get(int $id): ?User public function getUser(int $userId): ?User
{ {
return $this->userRepository->getUser($id); return $this->entityManagerInterface
->getRepository(User::class)
->getUser($userId);
} }
public function identicon( public function identicon(
@ -86,9 +89,52 @@ class UserService
return $identicon->getImageDataUri($format); return $identicon->getImageDataUri($format);
} }
public function save(User $user) : void public function save(User $user) : void // @TODO delete
{ {
$this->entityManager->persist($user); $this->entityManagerInterface->persist($user);
$this->entityManager->flush(); $this->entityManagerInterface->flush();
}
// User star
public function findUserStar(
int $userId,
int $userIdTarget
): ?UserStar
{
return $this->entityManagerInterface
->getRepository(UserStar::class)
->findUserStar($userId, $userIdTarget);
}
public function findUserStarsTotalByUserIdTarget(int $torrentId): int
{
return $this->entityManagerInterface
->getRepository(UserStar::class)
->findUserStarsTotalByUserIdTarget($torrentId);
}
public function toggleUserStar(
int $userId,
int $userIdTarget,
int $added
): void
{
if ($userStar = $this->findUserStar($userId, $userIdTarget))
{
$this->entityManagerInterface->remove($userStar);
$this->entityManagerInterface->flush();
}
else
{
$userStar = new UserStar();
$userStar->setUserId($userId);
$userStar->setUserIdTarget($userIdTarget);
$userStar->setAdded($added);
$this->entityManagerInterface->persist($userStar);
$this->entityManagerInterface->flush();
}
} }
} }

24
templates/default/user/info.html.twig

@ -2,10 +2,30 @@
{% block title %}{{ 'User'|trans }} #{{ user.id }} - {{ name }}{% endblock %} {% block title %}{{ 'User'|trans }} #{{ user.id }} - {{ name }}{% endblock %}
{% block main_content %} {% block main_content %}
<div class="padding-24-px margin-y-8-px border-radius-3-px background-color-night"> <div class="padding-24-px margin-y-8-px border-radius-3-px background-color-night">
<div class="text-center"> <div class="margin-b-16-px text-center">
<img class="border-radius-50 border-color-default border-width-2-px" src="{{ user.identicon }}" alt="{{ 'identicon'|trans }}" /> <img class="border-radius-50 border-color-default border-width-2-px" src="{{ user.identicon }}" alt="{{ 'identicon'|trans }}" />
</div> </div>
<h2>{{ 'Profile'|trans }}</h2> <div class="margin-b-16-px">
<h1 class="display-block text-center margin-b-16-px">
{{ 'User'|trans }} #{{ user.id }}
</h1>
</div>
<div class="text-center">
<a class="margin-l-8-px margin-r-4-px" href="{{ path('user_star_toggle', {userId : user.id}) }}" title="{{ 'Bookmark' | trans }}">
{% if user.star.exist %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/>
</svg>
{% endif %}
</a>
<sup class="cursor-default" title="{{ 'Total' | trans }}">
{{ user.star.total }}
</sup>
</div>
<table class="width-100"> <table class="width-100">
<tbody> <tbody>
<tr> <tr>

Loading…
Cancel
Save