feat: stores user's media preferences

This commit is contained in:
2025-04-29 16:17:40 -05:00
parent 0225bead60
commit c3eaf109e3
15 changed files with 429 additions and 31 deletions

View File

@@ -4,15 +4,47 @@ namespace App\User\Action\Handler;
use App\User\Action\Command\SaveUserMediaPreferencesCommand;
use App\User\Action\Result\SaveUserMediaPreferencesResult;
use App\User\Framework\Entity\User;
use App\User\Framework\Entity\UserPreference;
use App\User\Framework\Repository\PreferencesRepository;
use Doctrine\ORM\EntityManagerInterface;
use OneToMany\RichBundle\Contract\CommandInterface as C;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface as R;
use Symfony\Bundle\SecurityBundle\Security;
/** @implements HandlerInterface<SaveUserMediaPreferencesCommand> */
class SaveUserMediaPreferencesHandler implements HandlerInterface
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly PreferencesRepository $preferenceRepository,
private readonly Security $token,
) {}
public function handle(C $command): R
{
return new SaveUserMediaPreferencesResult('Success');
/** @var User $user */
$user = $this->token->getUser();
foreach ($command as $preference => $value) {
if ($user->hasUserPreference($preference)) {
$user->updateUserPreference($preference, $value);
$this->entityManager->flush();
continue;
}
$preference = $this->preferenceRepository->find($preference);
$user->addUserPreference(
(new UserPreference())
->setUser($user)
->setPreference($preference)
->setPreferenceValue($value)
);
}
$this->entityManager->flush();
return new SaveUserMediaPreferencesResult($user->getUserPreferences());
}
}

View File

@@ -4,6 +4,7 @@ namespace App\User\Action\Input;
use App\User\Action\Command\SaveUserMediaPreferencesCommand;
use OneToMany\RichBundle\Attribute\SourceRequest;
use OneToMany\RichBundle\Attribute\SourceSecurity;
use OneToMany\RichBundle\Contract\CommandInterface as C;
use OneToMany\RichBundle\Contract\InputInterface;
@@ -11,6 +12,9 @@ use OneToMany\RichBundle\Contract\InputInterface;
class SaveUserMediaPreferencesInput implements InputInterface
{
public function __construct(
#[SourceSecurity]
public mixed $userId,
#[SourceRequest('resolution')]
public string $resolution,

View File

@@ -2,12 +2,13 @@
namespace App\User\Action\Result;
use Doctrine\Common\Collections\Collection;
use OneToMany\RichBundle\Contract\ResultInterface;
/** @implements ResultInterface */
class SaveUserMediaPreferencesResult implements ResultInterface
{
public function __construct(
public string $status,
public Collection $userPreferences,
) {}
}

View File

@@ -4,10 +4,15 @@ declare(strict_types=1);
namespace App\User\Framework\Controller\Web;
use Aimeos\Map;
use App\User\Action\Handler\SaveUserMediaPreferencesHandler;
use App\User\Action\Input\SaveUserMediaPreferencesInput;
use App\User\Framework\Entity\User;
use App\User\Framework\Entity\UserPreference;
use App\User\Framework\Repository\PreferencesRepository;
use App\Util\CountryCodes;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
@@ -15,16 +20,29 @@ class PreferencesController extends AbstractController
{
public function __construct(
private readonly PreferencesRepository $preferencesRepository,
private readonly SaveUserMediaPreferencesHandler $saveUserMediaPreferencesHandler,
private readonly Security $security,
) {}
#[Route('/media/preferences', 'app_media_preferences', methods: ['GET'])]
public function mediaPreferences(): Response
{
$enabledPreferences = $this->preferencesRepository->findEnabled();
if ($this->security->getUser()->getUserPreferences()->count() !== count($enabledPreferences)) {
$this->setUserPreferences($this->security->getUser(), $enabledPreferences);
}
$userPreferences = $this->security->getUser()->getUserPreferences()->toArray();
$userPreferences = Map::from($userPreferences)
->rekey(fn($preference) => $preference->getPreference()->getId());
return $this->render(
'user/preferences.html.twig',
[
'preferences' => $this->preferencesRepository->findEnabled(),
'languages' => CountryCodes::$countries,
'providers' => ['test' => 'Test'],
'userPreferences' => $userPreferences->toArray(),
]
);
}
@@ -34,12 +52,31 @@ class PreferencesController extends AbstractController
SaveUserMediaPreferencesInput $input,
): Response
{
dd($input);
$userPreferences = $this->saveUserMediaPreferencesHandler->handle($input->toCommand())->userPreferences;
$userPreferences = Map::from($userPreferences)->rekey(fn($preference) => $preference->getPreference()->getId());
return $this->render(
'user/preferences.html.twig',
[
'preferences' => $this->preferencesRepository->findEnabled(),
'languages' => CountryCodes::$countries,
'providers' => ['test' => 'Test'],
'userPreferences' => $userPreferences->toArray(),
]
);
}
private function setUserPreferences(User $user, array $preferences): void
{
foreach ($preferences as $preference) {
if (false === $user->hasUserPreference($preference->getId())) {
$user->addUserPreference((new UserPreference())
->setUser($user)
->setPreference($preference)
->setPreferenceValue(null)
);
}
}
$this->preferencesRepository->getEntityManager()->flush();
}
}

View File

@@ -3,7 +3,9 @@
namespace App\User\Framework\Controller\Web;
use App\User\Framework\Entity\User;
use App\User\Framework\Entity\UserPreference;
use App\User\Framework\Form\RegistrationFormType;
use App\User\Framework\Repository\PreferencesRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
@@ -14,8 +16,12 @@ use Symfony\Component\Routing\Attribute\Route;
class RegistrationController extends AbstractController
{
#[Route('/register', name: 'app_register')]
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
{
public function register(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
EntityManagerInterface $entityManager,
PreferencesRepository $preferencesRepository,
): Response {
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
@@ -30,7 +36,9 @@ class RegistrationController extends AbstractController
$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email
$this->setUserPreferences($user, $preferencesRepository->findEnabled());
$preferencesRepository->getEntityManager()->flush();
return $this->redirectToRoute('app_index');
}
@@ -39,4 +47,15 @@ class RegistrationController extends AbstractController
'registrationForm' => $form,
]);
}
private function setUserPreferences(User $user, array $preferences): void
{
foreach ($preferences as $preference) {
$user->addUserPreference((new UserPreference())
->setUser($user)
->setPreference($preference)
->setPreferenceValue(null)
);
}
}
}

View File

@@ -26,7 +26,7 @@ class Preference
/**
* @var Collection<int, PreferenceOption>
*/
#[ORM\OneToMany(targetEntity: PreferenceOption::class, mappedBy: 'preference')]
#[ORM\OneToMany(targetEntity: PreferenceOption::class, mappedBy: 'preference', fetch: 'EAGER')]
private Collection $preferenceOptions;
public function __construct()

View File

@@ -2,13 +2,17 @@
namespace App\User\Framework\Entity;
use App\User\Framework\Repository\PreferencesRepository;
use App\User\Framework\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
@@ -29,6 +33,17 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(type: 'string')]
private string $password;
/**
* @var Collection<int, UserPreference>
*/
#[ORM\OneToMany(targetEntity: UserPreference::class, mappedBy: 'user', cascade: ['persist', 'remove'])]
private Collection $userPreferences;
public function __construct()
{
$this->userPreferences = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
@@ -110,4 +125,63 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* @return Collection<int, UserPreference>
*/
public function getUserPreferences(): Collection
{
return $this->userPreferences;
}
public function getUserPreference(string $preferenceName)
{
foreach ($this->userPreferences as $userPreference) {
if ($userPreference->getPreference()->getName() === $preferenceName) {
return $userPreference->getPreference();
}
}
}
public function hasUserPreference(string $preferenceName): bool
{
foreach ($this->userPreferences as $userPreference) {
if ($userPreference->getPreference()->getId() === $preferenceName) {
return true;
}
}
return false;
}
public function updateUserPreference(string $preferenceName, mixed $preferenceValue): static
{
foreach ($this->userPreferences as $userPreference) {
if ($userPreference->getPreference()->getId() === $preferenceName) {
$userPreference->setPreferenceValue($preferenceValue);
}
}
return $this;
}
public function addUserPreference(UserPreference $userPreference): static
{
if (!$this->userPreferences->contains($userPreference)) {
$this->userPreferences->add($userPreference);
$userPreference->setUser($this);
}
return $this;
}
public function removeUserPreference(UserPreference $userPreference): static
{
if ($this->userPreferences->removeElement($userPreference)) {
// set the owning side to null (unless already changed)
if ($userPreference->getUser() === $this) {
$userPreference->setUser(null);
}
}
return $this;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\User\Framework\Entity;
use App\User\Framework\Repository\UserPreferenceRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserPreferenceRepository::class)]
class UserPreference
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'userPreferences')]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
#[ORM\ManyToOne(fetch: 'EAGER')]
#[ORM\JoinColumn(nullable: false)]
private ?Preference $preference = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $preference_value = null;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): static
{
$this->user = $user;
return $this;
}
public function getPreference(): ?Preference
{
return $this->preference;
}
public function setPreference(?Preference $preference): static
{
$this->preference = $preference;
return $this;
}
public function getPreferenceValue(): ?string
{
return $this->preference_value;
}
public function setPreferenceValue(?string $preference_value): static
{
$this->preference_value = $preference_value;
return $this;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\User\Framework\Repository;
use App\User\Framework\Entity\UserPreference;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<UserPreference>
*/
class UserPreferenceRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, UserPreference::class);
}
public function findUserPreferences(int $userId): array
{
return $this->findBy(['userId' => $userId]);
}
public function findUserResolution(int $userId): ?UserPreference
{
return $this->findOneBy(['userId' => $userId, 'preference' => 'resolution']);
}
public function findUserCodec(int $userId): ?UserPreference
{
return $this->findOneBy(['userId' => $userId, 'preference' => 'codec']);
}
public function findUserLanguage(int $userId): ?UserPreference
{
return $this->findOneBy(['userId' => $userId, 'preference' => 'language']);
}
public function findUserProvider(int $userId): ?UserPreference
{
return $this->findOneBy(['userId' => $userId, 'preference' => 'resolution']);
}
}