Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions migrations/Version20260722201120.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20260722201120 extends AbstractMigration
{
public function getDescription(): string
{
return "Add user collections";
}

public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE usercollectionitems (id INT AUTO_INCREMENT NOT NULL, collection INT NOT NULL, type ENUM(\'show\', \'season\', \'episode\', \'movie\', \'movieset\') NOT NULL, item INT NOT NULL, addedAt DATETIME NOT NULL, INDEX IDX_41B0376EFC4D6532 (collection), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE usercollections (id INT AUTO_INCREMENT NOT NULL, user INT NOT NULL, name VARCHAR(255) NOT NULL, createdAt DATETIME NOT NULL, INDEX IDX_1709E57A8D93D649 (user), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE usercollectionitems ADD CONSTRAINT FK_41B0376EFC4D6532 FOREIGN KEY (collection) REFERENCES usercollections (id)');
$this->addSql('ALTER TABLE usercollections ADD CONSTRAINT FK_1709E57A8D93D649 FOREIGN KEY (user) REFERENCES users (id)');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE usercollectionitems DROP FOREIGN KEY FK_41B0376EFC4D6532');
$this->addSql('ALTER TABLE usercollections DROP FOREIGN KEY FK_1709E57A8D93D649');
$this->addSql('DROP TABLE usercollectionitems');
$this->addSql('DROP TABLE usercollections');
}
}
11 changes: 11 additions & 0 deletions src/main/php/tracky/UserCollectionItemType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
namespace tracky;

enum UserCollectionItemType: string
{
case SHOW = "show";
case SEASON = "season";
case EPISODE = "episode";
case MOVIE = "movie";
case MOVIE_SET = "movieset";
}
120 changes: 120 additions & 0 deletions src/main/php/tracky/controller/UserCollectionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
namespace tracky\controller;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use tracky\datetime\DateTime;
use tracky\model\BaseEntity;
use tracky\model\User;
use tracky\model\UserCollection;
use tracky\model\UserCollectionItem;
use tracky\orm\EpisodeRepository;
use tracky\orm\MovieRepository;
use tracky\orm\MovieSetRepository;
use tracky\orm\SeasonRepository;
use tracky\orm\ShowRepository;
use tracky\orm\UserCollectionRepository;
use tracky\UserCollectionItemType;

class UserCollectionController extends AbstractController
{
#[Route("/users/{username}/collections", name: "user_profile_collections_page", methods: ["GET"])]
public function getListPage(User $user, UserCollectionRepository $userCollectionRepository): Response
{
return $this->render("user/collections/collections.twig", [
"user" => $user,
"collections" => $userCollectionRepository->findBy(["user" => $user])
]);
}

#[Route("/users/{username}/collections/{collection}", name: "user_profile_collection_page", methods: ["GET"])]
public function getCollectionPage(
User $user,
UserCollection $collection,
ShowRepository $showRepository,
SeasonRepository $seasonRepository,
EpisodeRepository $episodeRepository,
MovieRepository $movieRepository,
MovieSetRepository $movieSetRepository
): Response
{
$typeToRepositoryMap = [
UserCollectionItemType::SHOW->value => $showRepository,
UserCollectionItemType::SEASON->value => $seasonRepository,
UserCollectionItemType::EPISODE->value => $episodeRepository,
UserCollectionItemType::MOVIE->value => $movieRepository,
UserCollectionItemType::MOVIE_SET->value => $movieSetRepository
];

/**
* @var array<string, array<int, UserCollectionItem>>
*/
$perTypeItems = [];

foreach ($collection->getItems() as $item) {
$type = $item->getType()->value;

if (!isset($perTypeItems[$type])) {
$perTypeItems[$type] = [];
}

$perTypeItems[$type][$item->getId()] = $item;
}

foreach ($perTypeItems as $type => $items) {
$itemIds = array_map(fn($item) => $item->getItem(), $items);

/**
* @var BaseEntity $resolvedItem
*/
foreach ($typeToRepositoryMap[$type]->findByIds($itemIds) as $resolvedItem) {
$index = array_search($resolvedItem->getId(), $itemIds);
$items[$index]->setResolvedItem($resolvedItem);
}
}

return $this->render("user/collections/collection.twig", [
"user" => $user,
"collection" => $collection
]);
}

#[Route("/users/{username}/collections", name: "user_profile_collection_create_action", methods: ["POST"])]
#[IsGranted("IS_AUTHENTICATED")]
public function createCollection(User $user, Request $request, UserCollectionRepository $userCollectionRepository, EntityManagerInterface $entityManagerInterface): Response
{
/**
* @var User
*/
$currentUser = $this->getUser();

if ($user->getId() !== $currentUser->getId()) {
throw new AccessDeniedHttpException;
}

$name = trim($request->request->getString("name"));

if ($name === "") {
return $this->redirectToRoute("user_profile_collections_page", ["username" => $user->getUsername(), "flash" => "error", "error" => "empty-name", "name" => $name]);
}

if ($userCollectionRepository->count(["user" => $user, "name" => $name])) {
return $this->redirectToRoute("user_profile_collections_page", ["username" => $user->getUsername(), "flash" => "error", "error" => "duplicate-collection", "name" => $name]);
}

$collection = new UserCollection;
$collection->setUser($user);
$collection->setName($name);
$collection->setCreatedAt(new DateTime);

$entityManagerInterface->persist($collection);
$entityManagerInterface->flush();

return $this->redirectToRoute("user_profile_collections_page", ["username" => $user->getUsername(), "flash" => "success", "name" => $name]);
}
}
77 changes: 77 additions & 0 deletions src/main/php/tracky/model/UserCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
namespace tracky\model;

use Doctrine\ORM\Mapping as ORM;
use tracky\datetime\DateTime;
use tracky\orm\UserCollectionRepository;

#[ORM\Entity(repositoryClass: UserCollectionRepository::class)]
#[ORM\Table(name: "usercollections")]
class UserCollection extends BaseEntity
{
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: "user", referencedColumnName: "id", nullable: false)]
protected User $user;

#[ORM\Column(type: "string", nullable: false)]
private string $name;

#[ORM\Column(name: "createdAt", type: "datetime", nullable: false)]
private DateTime $createdAt;

#[ORM\OneToMany(mappedBy: "collection", targetEntity: UserCollectionItem::class, cascade: ["persist"])]
private mixed $items = [];

public function getUser(): User
{
return $this->user;
}

public function setUser(User $user): self
{
$this->user = $user;
return $this;
}

public function getName(): string
{
return $this->name;
}

public function setName(string $name): self
{
$this->name = $name;
return $this;
}

public function getCreatedAt(): DateTime
{
return $this->createdAt;
}

public function setCreatedAt(DateTime $date): self
{
$this->createdAt = $date;
return $this;
}

/**
* @return UserCollectionItem[]
*/
public function getItems(): mixed
{
return $this->items;
}

public function setItems(mixed $items): self
{
$this->items = $items;
return $this;
}

public function addItem(UserCollectionItem $item): self
{
$this->items[] = $item;
return $this;
}
}
91 changes: 91 additions & 0 deletions src/main/php/tracky/model/UserCollectionItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
namespace tracky\model;

use Doctrine\ORM\Mapping as ORM;
use tracky\datetime\DateTime;
use tracky\orm\UserCollectionItemRepository;
use tracky\UserCollectionItemType;

#[ORM\Entity(repositoryClass: UserCollectionItemRepository::class)]
#[ORM\Table(
name: "usercollectionitems",
uniqueConstraints: [
new ORM\UniqueConstraint(
name: "unique_collection_type_item",
columns: ["collection", "type", "item"]
)
]
)]
class UserCollectionItem extends BaseEntity
{
#[ORM\ManyToOne(targetEntity: UserCollection::class)]
#[ORM\JoinColumn(name: "collection", referencedColumnName: "id", nullable: false)]
private UserCollection $collection;

#[ORM\Column(name: "type", enumType: UserCollectionItemType::class, type: "string", columnDefinition: "ENUM('show', 'season', 'episode', 'movie', 'movieset') NOT NULL")]
protected UserCollectionItemType $type;

#[ORM\Column(name: "item", type: "integer", nullable: false)]
protected int $item;

#[ORM\Column(name: "addedAt", type: "datetime", nullable: false)]
protected DateTime $addedAt;

protected ?BaseEntity $resolvedItem = null;

public function getCollection(): UserCollection
{
return $this->collection;
}

public function setCollection(UserCollection $collection): self
{
$this->collection = $collection;
return $this;
}

public function getType(): UserCollectionItemType
{
return $this->type;
}

public function setType(UserCollectionItemType $type): self
{
$this->type = $type;
return $this;
}

public function getItem(): int
{
return $this->item;
}

public function setItem(Show|Season|Episode|Movie|MovieSet $item): self
{
$this->item = $item->getId();
$this->resolvedItem = $item;
return $this;
}

public function getAddedAt(): DateTime
{
return $this->addedAt;
}

public function setAddedAt(DateTime $date): self
{
$this->addedAt = $date;
return $this;
}

public function getResolvedItem(): BaseEntity
{
return $this->resolvedItem;
}

public function setResolvedItem(Show|Season|Episode|Movie|MovieSet $item): self
{
$this->resolvedItem = $item;
return $this;
}
}
20 changes: 20 additions & 0 deletions src/main/php/tracky/orm/AbstractRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
namespace tracky\orm;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

abstract class AbstractRepository extends ServiceEntityRepository
{
public function findByIds(array $ids): array
{
if (empty($ids)) {
return [];
}

$queryBuilder = $this->createQueryBuilder("entity");

$queryBuilder->where($queryBuilder->expr()->in("entity.id", $ids));

return $queryBuilder->getQuery()->getResult();
}
}
19 changes: 1 addition & 18 deletions src/main/php/tracky/orm/EpisodeRepository.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?php
namespace tracky\orm;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use tracky\model\Episode;

class EpisodeRepository extends ServiceEntityRepository
class EpisodeRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
Expand Down Expand Up @@ -34,20 +33,4 @@ public function search(string $query, ?int $showId = null)
->getQuery()
->getResult();
}

/**
* @return Episode[]
*/
public function findByIds(array $ids): array
{
if (empty($ids)) {
return [];
}

$queryBuilder = $this->createQueryBuilder("episode");

$queryBuilder->where($queryBuilder->expr()->in("episode.id", $ids));

return $queryBuilder->getQuery()->getResult();
}
}
Loading
Loading