First work on the tournaments

Hecht 9 months ago
parent d76534d6a3
commit 6ba77d3cec

@ -109,7 +109,7 @@
<normalizationContext><values><value name="groups"><values><value>public</value></values></value></values></normalizationContext> <normalizationContext><values><value name="groups"><values><value>public</value></values></value></values></normalizationContext>
<operations> <operations>
<operation class="ApiPlatform\Metadata\Get" /> <operation class="ApiPlatform\Metadata\Get" />
<operation class="ApiPlatform\Metadata\GetCollection" controller="App\Controller\GetDojoCharacters" uriTemplate="dojo/{dojoId}/characters" description="Receives the characters from a dojo."> <operation class="ApiPlatform\Metadata\GetCollection" controller="App\Controller\GetDojoCharacters" uriTemplate="dojos/{dojoId}/characters" description="Receives the characters from a dojo.">
<uriVariables><uriVariable parameterName="dojoId" /></uriVariables> <uriVariables><uriVariable parameterName="dojoId" /></uriVariables>
</operation> </operation>
<operation class="ApiPlatform\Metadata\GetCollection" controller="App\Controller\GetOwnDojoCharacters" uriTemplate="dojo/characters" description="Receives the characters from the users dojo."> <operation class="ApiPlatform\Metadata\GetCollection" controller="App\Controller\GetOwnDojoCharacters" uriTemplate="dojo/characters" description="Receives the characters from the users dojo.">
@ -129,8 +129,29 @@
<operations> <operations>
<operation class="ApiPlatform\Metadata\Get" /> <operation class="ApiPlatform\Metadata\Get" />
<operation class="ApiPlatform\Metadata\GetCollection" /> <operation class="ApiPlatform\Metadata\GetCollection" />
<operation class="ApiPlatform\Metadata\Post" security="is_granted('ROLE_ADMIN')">
</operation>
</operations>
</resource>
<resource class="App\Entity\Tournament">
<operations>
<operation class="ApiPlatform\Metadata\Get" />
<operation class="ApiPlatform\Metadata\GetCollection" />
</operations>
</resource>
<resource class="App\Entity\TournamentRegistration">
<operations>
<operation class="ApiPlatform\Metadata\Post" /> <operation class="ApiPlatform\Metadata\Post" />
</operations> </operations>
</resource> </resource>
<resource class="App\Entity\Fight">
<operations>
<operation class="ApiPlatform\Metadata\Get" />
<operation class="ApiPlatform\Metadata\GetCollection" controller="App\Controller\GetTournamentFights" uriTemplate="tournament/{tournamentId}/fights" description="Receives the fights from a tournament."/>
</operations>
</resource>
</resources> </resources>

@ -10,8 +10,6 @@ security:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
main: main:
#custom_authenticators:
# - App\Security\ApiKeyAuthenticator
stateless: true stateless: true
access_token: access_token:

@ -1,112 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240219220159 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE "character" (id UUID NOT NULL, dojo_id UUID DEFAULT NULL, name VARCHAR(255) NOT NULL, strength INT NOT NULL, constitution INT NOT NULL, agility INT NOT NULL, chi INT NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_937AB03432F09E9C ON "character" (dojo_id)');
$this->addSql('COMMENT ON COLUMN "character".id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN "character".dojo_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE character_technique (character_id UUID NOT NULL, technique_id UUID NOT NULL, PRIMARY KEY(character_id, technique_id))');
$this->addSql('CREATE INDEX IDX_506B3A7A1136BE75 ON character_technique (character_id)');
$this->addSql('CREATE INDEX IDX_506B3A7A1F8ACB26 ON character_technique (technique_id)');
$this->addSql('COMMENT ON COLUMN character_technique.character_id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN character_technique.technique_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE city (id UUID NOT NULL, dungeon_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_2D5B0234B606863 ON city (dungeon_id)');
$this->addSql('COMMENT ON COLUMN city.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN city.dungeon_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE country (id UUID NOT NULL, capital_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_5373C966FC2D9FF7 ON country (capital_id)');
$this->addSql('COMMENT ON COLUMN country.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN country.capital_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE dojo (id UUID NOT NULL, village_id UUID DEFAULT NULL, owner_id UUID NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_9494CCB15E237E06 ON dojo (name)');
$this->addSql('CREATE INDEX IDX_9494CCB15E0D5582 ON dojo (village_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_9494CCB17E3C61F9 ON dojo (owner_id)');
$this->addSql('COMMENT ON COLUMN dojo.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN dojo.village_id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN dojo.owner_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE dungeon (id UUID NOT NULL, city_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_3FFA1F908BAC62AF ON dungeon (city_id)');
$this->addSql('COMMENT ON COLUMN dungeon.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN dungeon.city_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE prefecture (id UUID NOT NULL, capital_id UUID NOT NULL, country_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_ABE6511AFC2D9FF7 ON prefecture (capital_id)');
$this->addSql('CREATE INDEX IDX_ABE6511AF92F3E70 ON prefecture (country_id)');
$this->addSql('COMMENT ON COLUMN prefecture.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN prefecture.capital_id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN prefecture.country_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE technique (id UUID NOT NULL, prerequisite_id UUID DEFAULT NULL, costs INT NOT NULL, damage VARCHAR(255) NOT NULL, energy VARCHAR(255) NOT NULL, accuracy VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_D73B9841276AF86B ON technique (prerequisite_id)');
$this->addSql('COMMENT ON COLUMN technique.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN technique.prerequisite_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, dojo_id UUID DEFAULT NULL, auth_name VARCHAR(255) NOT NULL, properties JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D64932F09E9C ON "user" (dojo_id)');
$this->addSql('COMMENT ON COLUMN "user".id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN "user".dojo_id IS \'(DC2Type:ulid)\'');
$this->addSql('CREATE TABLE village (id UUID NOT NULL, prefecture_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_4E6C7FAA9D39C865 ON village (prefecture_id)');
$this->addSql('COMMENT ON COLUMN village.id IS \'(DC2Type:ulid)\'');
$this->addSql('COMMENT ON COLUMN village.prefecture_id IS \'(DC2Type:ulid)\'');
$this->addSql('ALTER TABLE "character" ADD CONSTRAINT FK_937AB03432F09E9C FOREIGN KEY (dojo_id) REFERENCES dojo (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE character_technique ADD CONSTRAINT FK_506B3A7A1136BE75 FOREIGN KEY (character_id) REFERENCES "character" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE character_technique ADD CONSTRAINT FK_506B3A7A1F8ACB26 FOREIGN KEY (technique_id) REFERENCES technique (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE city ADD CONSTRAINT FK_2D5B0234B606863 FOREIGN KEY (dungeon_id) REFERENCES dungeon (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE country ADD CONSTRAINT FK_5373C966FC2D9FF7 FOREIGN KEY (capital_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE dojo ADD CONSTRAINT FK_9494CCB15E0D5582 FOREIGN KEY (village_id) REFERENCES village (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE dojo ADD CONSTRAINT FK_9494CCB17E3C61F9 FOREIGN KEY (owner_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE dungeon ADD CONSTRAINT FK_3FFA1F908BAC62AF FOREIGN KEY (city_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE prefecture ADD CONSTRAINT FK_ABE6511AFC2D9FF7 FOREIGN KEY (capital_id) REFERENCES city (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE prefecture ADD CONSTRAINT FK_ABE6511AF92F3E70 FOREIGN KEY (country_id) REFERENCES country (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE technique ADD CONSTRAINT FK_D73B9841276AF86B FOREIGN KEY (prerequisite_id) REFERENCES technique (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT FK_8D93D64932F09E9C FOREIGN KEY (dojo_id) REFERENCES dojo (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE village ADD CONSTRAINT FK_4E6C7FAA9D39C865 FOREIGN KEY (prefecture_id) REFERENCES prefecture (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE "character" DROP CONSTRAINT FK_937AB03432F09E9C');
$this->addSql('ALTER TABLE character_technique DROP CONSTRAINT FK_506B3A7A1136BE75');
$this->addSql('ALTER TABLE character_technique DROP CONSTRAINT FK_506B3A7A1F8ACB26');
$this->addSql('ALTER TABLE city DROP CONSTRAINT FK_2D5B0234B606863');
$this->addSql('ALTER TABLE country DROP CONSTRAINT FK_5373C966FC2D9FF7');
$this->addSql('ALTER TABLE dojo DROP CONSTRAINT FK_9494CCB15E0D5582');
$this->addSql('ALTER TABLE dojo DROP CONSTRAINT FK_9494CCB17E3C61F9');
$this->addSql('ALTER TABLE dungeon DROP CONSTRAINT FK_3FFA1F908BAC62AF');
$this->addSql('ALTER TABLE prefecture DROP CONSTRAINT FK_ABE6511AFC2D9FF7');
$this->addSql('ALTER TABLE prefecture DROP CONSTRAINT FK_ABE6511AF92F3E70');
$this->addSql('ALTER TABLE technique DROP CONSTRAINT FK_D73B9841276AF86B');
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_8D93D64932F09E9C');
$this->addSql('ALTER TABLE village DROP CONSTRAINT FK_4E6C7FAA9D39C865');
$this->addSql('DROP TABLE "character"');
$this->addSql('DROP TABLE character_technique');
$this->addSql('DROP TABLE city');
$this->addSql('DROP TABLE country');
$this->addSql('DROP TABLE dojo');
$this->addSql('DROP TABLE dungeon');
$this->addSql('DROP TABLE prefecture');
$this->addSql('DROP TABLE technique');
$this->addSql('DROP TABLE "user"');
$this->addSql('DROP TABLE village');
}
}

@ -0,0 +1,21 @@
<?php
namespace App\Controller;
use App\Entity\Fight;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
class GetTournamentFights
{
public function __invoke($tournamentId, EntityManagerInterface $em): iterable
{
return $em->getRepository(Fight::class)->findBy([
'tournament' => $tournamentId
], [
'startDate' => 'ASC'
]);
}
}

@ -25,7 +25,7 @@ class Character extends Thing
#[ApiProperty(readableLink: false, writableLink: false)] #[ApiProperty(readableLink: false, writableLink: false)]
public ?Dojo $dojo; public ?Dojo $dojo;
#[Column] #[Column(length: 64)]
#[Groups('public')] #[Groups('public')]
public string $name; public string $name;

@ -22,7 +22,7 @@ class Dojo extends Thing
#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
#[OneToMany(targetEntity: Character::class, mappedBy: 'dojo')] #[OneToMany(targetEntity: Character::class, mappedBy: 'dojo')]
#[JoinColumn(onDelete: 'cascade', nullable: false)] #[JoinColumn(onDelete: 'cascade', nullable: false)]
public iterable $members; public Collection $members;
#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
#[ManyToOne()] #[ManyToOne()]
@ -62,6 +62,7 @@ class Dojo extends Thing
} }
/** /**
*
* @return Collection<int, Character> * @return Collection<int, Character>
*/ */
public function getMembers(): Collection public function getMembers(): Collection
@ -71,7 +72,7 @@ class Dojo extends Thing
public function addMember(Character $member): static public function addMember(Character $member): static
{ {
if (!$this->members->contains($member)) { if (! $this->members->contains($member)) {
$this->members->add($member); $this->members->add($member);
$member->setDojo($this); $member->setDojo($this);
} }

@ -0,0 +1,108 @@
<?php
namespace App\Entity;
use App\Repository\FightRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: FightRepository::class)]
class Fight extends Thing
{
#[ORM\ManyToMany(targetEntity: Character::class)]
private Collection $characters;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $startDate = null;
#[ORM\Column()]
private array $events = [];
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Character $winner = null;
#[ORM\ManyToOne(inversedBy: 'fights')]
#[ORM\JoinColumn(nullable: false, onDelete: 'cascade')]
private ?Tournament $tournament = null;
public function __construct()
{
$this->characters = new ArrayCollection();
}
/**
*
* @return Collection<int, Character>
*/
public function getCharacters(): Collection
{
return $this->characters;
}
public function addCharacter(Character $character): static
{
if (! $this->characters->contains($character)) {
$this->characters->add($character);
}
return $this;
}
public function removeCharacter(Character $character): static
{
$this->characters->removeElement($character);
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
public function setStartDate(\DateTimeInterface $startDate): static
{
$this->startDate = $startDate;
return $this;
}
public function getEvents(): array
{
return $this->events;
}
public function setEvents(array $events): static
{
$this->events = $events;
return $this;
}
public function getWinner(): ?Character
{
return $this->winner;
}
public function setWinner(?Character $winner): static
{
$this->winner = $winner;
return $this;
}
public function getTournament(): ?Tournament
{
return $this->tournament;
}
public function setTournament(?Tournament $tournament): static
{
$this->tournament = $tournament;
return $this;
}
}

@ -1,15 +1,19 @@
<?php <?php
namespace App\Entity; namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne; use Doctrine\ORM\Mapping\ManyToOne;
#[Entity(repositoryClass: 'App\Repository\TechniqueRepository')] #[Entity(repositoryClass: 'App\Repository\TechniqueRepository')]
class Technique extends Thing class Technique extends Thing
{ {
#[Column(unique: true)]
public string $name;
/** /**
* For now we calculate costs for mastering a technique. * For now we calculate costs for mastering a technique.
*/ */
@ -37,8 +41,9 @@ class Technique extends Thing
/** /**
* Technique that is required to be learned before this Technique. * Technique that is required to be learned before this Technique.
*/ */
#[OneToOne()] #[ManyToOne()]
#[JoinColumn(onDelete: 'SET NULL', nullable: true)] #[JoinColumn(onDelete: 'SET NULL', nullable: true)]
#[ApiProperty(readableLink: false, writableLink: false)]
public ?Technique $prerequisite; public ?Technique $prerequisite;
public function getCosts(): ?int public function getCosts(): ?int
@ -100,5 +105,17 @@ class Technique extends Thing
return $this; return $this;
} }
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
} }

@ -0,0 +1,111 @@
<?php
namespace App\Entity;
use App\Repository\TournamentRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TournamentRepository::class)]
class Tournament extends Thing
{
#[ORM\Column(length: 255)]
public string $name;
#[ORM\ManyToMany(targetEntity: Character::class)]
public Collection $characters;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $startDate = null;
#[ORM\OneToMany(mappedBy: 'tournament', targetEntity: Fight::class)]
private Collection $fights;
public function __construct()
{
$this->characters = new ArrayCollection();
$this->fights = new ArrayCollection();
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
/**
*
* @return Collection<int, Character>
*/
public function getCharacters(): Collection
{
return $this->characters;
}
public function addCharacter(Character $character): static
{
if (! $this->characters->contains($character)) {
$this->characters->add($character);
}
return $this;
}
public function removeCharacter(Character $character): static
{
$this->characters->removeElement($character);
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
public function setStartDate(\DateTimeInterface $startDate): static
{
$this->startDate = $startDate;
return $this;
}
/**
*
* @return Collection<int, Fight>
*/
public function getFights(): Collection
{
return $this->fights;
}
public function addFight(Fight $fight): static
{
if (! $this->fights->contains($fight)) {
$this->fights->add($fight);
$fight->setTournament($this);
}
return $this;
}
public function removeFight(Fight $fight): static
{
if ($this->fights->removeElement($fight)) {
// set the owning side to null (unless already changed)
if ($fight->getTournament() === $this) {
$fight->setTournament(null);
}
}
return $this;
}
}

@ -0,0 +1,11 @@
<?php
namespace App\Entity;
class TournamentRegistration
{
public Tournament $tournament;
public Character $character;
}

@ -16,7 +16,7 @@ class User extends Thing implements UserInterface
// from discord // from discord
#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
#[Column(type: 'string')] #[Column(length: 32)]
public string $authName; public string $authName;
#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
@ -71,9 +71,15 @@ class User extends Thing implements UserInterface
public function getRoles(): array public function getRoles(): array
{ {
return [ $array = [
"ROLE_USER" "ROLE_USER"
]; ];
if ($this->authName == 'dehecht' || $this->authName == '.radiskull') {
$array[] = 'ROLE_ADMIN';
}
return $array;
} }
public function getAuthName(): ?string public function getAuthName(): ?string

@ -0,0 +1,71 @@
<?php
namespace App\Factory;
use App\Entity\Fight;
use App\Repository\FightRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Proxy;
use Zenstruck\Foundry\RepositoryProxy;
/**
* @extends ModelFactory<Fight>
*
* @method Fight|Proxy create(array|callable $attributes = [])
* @method static Fight|Proxy createOne(array $attributes = [])
* @method static Fight|Proxy find(object|array|mixed $criteria)
* @method static Fight|Proxy findOrCreate(array $attributes)
* @method static Fight|Proxy first(string $sortedField = 'id')
* @method static Fight|Proxy last(string $sortedField = 'id')
* @method static Fight|Proxy random(array $attributes = [])
* @method static Fight|Proxy randomOrCreate(array $attributes = [])
* @method static FightRepository|RepositoryProxy repository()
* @method static Fight[]|Proxy[] all()
* @method static Fight[]|Proxy[] createMany(int $number, array|callable $attributes = [])
* @method static Fight[]|Proxy[] createSequence(iterable|callable $sequence)
* @method static Fight[]|Proxy[] findBy(array $attributes)
* @method static Fight[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
* @method static Fight[]|Proxy[] randomSet(int $number, array $attributes = [])
*/
final class FightFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'events' => [],
'startDate' => self::faker()->dateTime(),
'tournament' => TournamentFactory::new(),
'winner' => CharacterFactory::new(),
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(Fight $fight): void {})
;
}
protected static function getClass(): string
{
return Fight::class;
}
}

@ -47,6 +47,7 @@ final class TechniqueFactory extends ModelFactory
protected function getDefaults(): array protected function getDefaults(): array
{ {
return [ return [
'name' => self::faker()->ean13(),
'accuracy' => self::faker()->text(), 'accuracy' => self::faker()->text(),
'costs' => self::faker()->numberBetween(1, 2), 'costs' => self::faker()->numberBetween(1, 2),
'damage' => self::faker()->text(), 'damage' => self::faker()->text(),

@ -0,0 +1,69 @@
<?php
namespace App\Factory;
use App\Entity\Tournament;
use App\Repository\TournamentRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Proxy;
use Zenstruck\Foundry\RepositoryProxy;
/**
* @extends ModelFactory<Tournament>
*
* @method Tournament|Proxy create(array|callable $attributes = [])
* @method static Tournament|Proxy createOne(array $attributes = [])
* @method static Tournament|Proxy find(object|array|mixed $criteria)
* @method static Tournament|Proxy findOrCreate(array $attributes)
* @method static Tournament|Proxy first(string $sortedField = 'id')
* @method static Tournament|Proxy last(string $sortedField = 'id')
* @method static Tournament|Proxy random(array $attributes = [])
* @method static Tournament|Proxy randomOrCreate(array $attributes = [])
* @method static TournamentRepository|RepositoryProxy repository()
* @method static Tournament[]|Proxy[] all()
* @method static Tournament[]|Proxy[] createMany(int $number, array|callable $attributes = [])
* @method static Tournament[]|Proxy[] createSequence(iterable|callable $sequence)
* @method static Tournament[]|Proxy[] findBy(array $attributes)
* @method static Tournament[]|Proxy[] randomRange(int $min, int $max, array $attributes = [])
* @method static Tournament[]|Proxy[] randomSet(int $number, array $attributes = [])
*/
final class TournamentFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'name' => self::faker()->text(255),
'startDate' => self::faker()->dateTime(),
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(Tournament $tournament): void {})
;
}
protected static function getClass(): string
{
return Tournament::class;
}
}

@ -47,7 +47,7 @@ final class UserFactory extends ModelFactory
protected function getDefaults(): array protected function getDefaults(): array
{ {
return [ return [
'authName' => self::faker()->text(), 'authName' => self::faker()->userName(),
'properties' => [] 'properties' => []
]; ];
} }

@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\Fight;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Fight>
*
* @method Fight|null find($id, $lockMode = null, $lockVersion = null)
* @method Fight|null findOneBy(array $criteria, array $orderBy = null)
* @method Fight[] findAll()
* @method Fight[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FightRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Fight::class);
}
// /**
// * @return Fight[] Returns an array of Fight objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('f')
// ->andWhere('f.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('f.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Fight
// {
// return $this->createQueryBuilder('f')
// ->andWhere('f.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\Tournament;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Tournament>
*
* @method Tournament|null find($id, $lockMode = null, $lockVersion = null)
* @method Tournament|null findOneBy(array $criteria, array $orderBy = null)
* @method Tournament[] findAll()
* @method Tournament[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class TournamentRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Tournament::class);
}
// /**
// * @return Tournament[] Returns an array of Tournament objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('t')
// ->andWhere('t.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('t.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Tournament
// {
// return $this->createQueryBuilder('t')
// ->andWhere('t.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

@ -1,68 +0,0 @@
<?php
namespace App\Security;
use App\Repository\UserRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class ApiKeyAuthenticator extends AbstractAuthenticator
{
public function __construct(private UserRepository $userRepository, private LoggerInterface $logger)
{}
/**
* Called on every request to decide if this authenticator should be
* used for the request.
* Returning false will cause this authenticator
* to be skipped.
*/
public function supports(Request $request): ?bool
{
return $request->headers->has('X-AUTH-TOKEN');
}
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-AUTH-TOKEN');
if (null === $apiToken) {
return null;
}
$userIdentifier = $apiToken;
return new SelfValidatingPassport(
new UserBadge($userIdentifier,
function (string $userIdentifier): ?UserInterface {
return $this->userRepository->findOneBy([
'authName' => $userIdentifier
]);
}));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$this->logger->critical("YYY");
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
// This should translated by FOSRestBundle!
throw new AccessDeniedHttpException($message);
}
}

@ -3,6 +3,8 @@ namespace App\Tests;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Symfony\Bundle\Test\Client; use ApiPlatform\Symfony\Bundle\Test\Client;
use App\Entity\Thing;
use Zenstruck\Foundry\Proxy;
use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Test\ResetDatabase;
use DateTimeZone; use DateTimeZone;
@ -28,7 +30,7 @@ abstract class AbstractTest extends ApiTestCase
return sodium_bin2base64(sodium_crypto_sign($message, $sign_secret), SODIUM_BASE64_VARIANT_URLSAFE); return sodium_bin2base64(sodium_crypto_sign($message, $sign_secret), SODIUM_BASE64_VARIANT_URLSAFE);
} }
protected static function createClientWithToken($authName = null): Client protected static function createClientWithToken($authName = "FooMan"): Client
{ {
return static::createClient([], return static::createClient([],
[ [
@ -38,5 +40,13 @@ abstract class AbstractTest extends ApiTestCase
] ]
]); ]);
} }
protected function getIri(Thing|Proxy $thing)
{
if ($thing instanceof Proxy) {
return $this->getIriFromResource($thing->object());
}
return $this->getIriFromResource($thing);
}
} }

@ -22,7 +22,7 @@ class CharacterTest extends AbstractTest
CharacterFactory::createMany(10); CharacterFactory::createMany(10);
$response = static::createClientWithToken($requestUser->authName)->request('GET', $response = static::createClientWithToken($requestUser->authName)->request('GET',
'/api/dojo/' . $dojo->id . '/characters'); $this->getIri($dojo) . '/characters');
$this->assertResponseStatusCodeSame(200); $this->assertResponseStatusCodeSame(200);
@ -115,8 +115,7 @@ class CharacterTest extends AbstractTest
'agility' => 1, 'agility' => 1,
'chi' => 1, 'chi' => 1,
'techniques' => [ 'techniques' => [
'/api/techniques/' . $tech->getId() $this->getIri($tech)
->toBase32()
] ]
] ]
]); ]);
@ -149,8 +148,7 @@ class CharacterTest extends AbstractTest
'agility' => 1, 'agility' => 1,
'chi' => 1, 'chi' => 1,
'techniques' => [ 'techniques' => [
'/api/techniques/' . $tech->getId() $this->getIri($tech)
->toBase32()
] ]
] ]
]); ]);
@ -181,8 +179,7 @@ class CharacterTest extends AbstractTest
'agility' => 1, 'agility' => 1,
'chi' => 1, 'chi' => 1,
'techniques' => [ 'techniques' => [
'/api/techniques/' . $tech->getId() $this->getIri($tech)
->toBase32()
] ]
] ]
]); ]);

@ -53,8 +53,7 @@ class DojoTest extends AbstractTest
[ [
'name' => 'BigFightDojo', 'name' => 'BigFightDojo',
'members' => [], 'members' => [],
'owner' => '/api/users/' . $dojo->owner->getId() 'owner' => $this->getIri($dojo->getOwner()),
->toBase32(),
'id' => $dojo->getId() 'id' => $dojo->getId()
->toBase32() ->toBase32()
]); ]);

@ -0,0 +1,134 @@
<?php
namespace App\Tests;
use App\Factory\TechniqueFactory;
class TechniqueTest extends AbstractTest
{
public function testListTechniques(): void
{
TechniqueFactory::createMany(10, [
'prerequisite' => NULL
]);
$response = static::createClientWithToken()->request('GET', '/api/techniques');
$this->assertEquals(10, count($response->toArray()));
}
public function testListTechniquesWithPrerequisite(): void
{
$prerequisite = TechniqueFactory::createOne([
'prerequisite' => NULL
]);
TechniqueFactory::createMany(2, [
'prerequisite' => $prerequisite
]);
$response = static::createClientWithToken()->request('GET', '/api/techniques');
$this->assertEquals(3, count($response->toArray()));
}
public function testShowTechnique(): void
{
$technique = TechniqueFactory::createOne([
'prerequisite' => NULL
]);
$response = static::createClientWithToken()->request('GET',
'/api/techniques/' . $technique->getId()
->toBase32());
$this->assertJsonEquals(
[
'id' => $technique->getId()
->toBase32(),
'name' => $technique->getName(),
'costs' => $technique->getCosts(),
'damage' => $technique->getDamage(),
'energy' => $technique->getEnergy(),
'accuracy' => $technique->getAccuracy()
], $response->getContent());
}
public function testShowTechniqueWithPrerequisite(): void
{
$prerequisite = TechniqueFactory::createOne([
'prerequisite' => NULL
]);
$technique = TechniqueFactory::createOne([
'prerequisite' => $prerequisite
]);
$response = static::createClientWithToken()->request('GET',
'/api/techniques/' . $technique->getId()
->toBase32());
$this->assertJsonEquals(
[
'id' => $technique->getId()
->toBase32(),
'name' => $technique->getName(),
'costs' => $technique->getCosts(),
'damage' => $technique->getDamage(),
'energy' => $technique->getEnergy(),
'accuracy' => $technique->getAccuracy(),
'prerequisite' => '/api/techniques/' . $prerequisite->getId()
->toBase32()
], $response->getContent());
}
public function testCreateTechniqueAsAdmin(): void
{
$response = static::createClientWithToken("dehecht")->request('POST', '/api/techniques',
[
'json' => [
'name' => 'Drei-Schwert-Style',
'costs' => 2,
'damage' => '3 * strength',
'energy' => '1.5 * constitution + 2 * strength',
'accuracy' => '2 * agility'
]
]);
$this->assertResponseStatusCodeSame(201);
$this->assertArrayHasKey('id', $response->toArray());
}
public function testCreateTechniqueFailsAsUser(): void
{
static::createClientWithToken()->request('POST', '/api/techniques',
[
'json' => [
'name' => 'Drei-Schwert-Style',
'costs' => 2,
'damage' => '3 * strength',
'energy' => '1.5 * constitution + 2 * strength',
'accuracy' => '2 * agility'
]
]);
$this->assertResponseStatusCodeSame(403);
}
public function testFailToCreateTechniqueWithNonExistentPrerequisite(): void
{
$response = static::createClientWithToken("dehecht")->request('POST', '/api/techniques',
[
'json' => [
'name' => 'Drei-Schwert-Style',
'costs' => 2,
'damage' => '3 * strength',
'energy' => '1.5 * constitution + 2 * strength',
'accuracy' => '2 * agility',
'prerequisite' => '/api/techniques/01ARZ3NDEKTSV4RRFFQ69G5FAV'
]
]);
$this->assertTrue($response->getStatusCode() / 100 != 2);
}
}

@ -0,0 +1,71 @@
<?php
namespace App\Tests;
use App\Factory\CharacterFactory;
use App\Factory\DojoFactory;
use App\Factory\TournamentFactory;
use App\Factory\UserFactory;
class TournamentTest extends AbstractTest
{
// No need to create tournaments via API Endpoint ... will be done in cronjob
public function testRegisterCharacter(): void
{
$tournament = TournamentFactory::createOne();
$dojo = DojoFactory::createOne([
'owner' => UserFactory::createOne()
]);
$character = CharacterFactory::createOne([
'dojo' => $dojo
]);
$response = static::createClientWithToken($dojo->getOwner()->authName)->request('POST',
'/api/tournament_registrations',
[
'json' => [
'tournament' => $this->getIri($tournament),
'character' => $this->getIri($character)
]
]);
$this->assertResponseStatusCodeSame(201);
$this->assertArrayHasKey('id', $response->toArray());
}
public function testRegisterCharacterNotPossibleTwice(): void
{}
public function testRegisterCharacterOnPastTournament(): void
{}
public function testShowRegisteredCharacters(): void
{}
public function testListTournaments(): void
{}
/**
* Status is ...
* meta data like when it is starting, name, "location", Winner (nullable), etc.
*/
public function testTournamentStatus(): void
{}
// /api/tournament/{id}/fights
// readableLink: true -> participant ids and winner
public function testTournamentFights(): void
{}
// /api/tournament/{id}/character/{id}/fights
public function testTournamentFightsForCharacter(): void
{}
// /api/tournament/{id}/ranking
public function testTournamentRanking(): void
{}
}
Loading…
Cancel
Save