5 Commity 9af2810be3 ... d5ff58e9d4

Autor SHA1 Wiadomość Data
  olinox14 d5ff58e9d4 add concept.md 10 miesięcy temu
  olinox14 e68572b2e7 update vuetify and fix config 10 miesięcy temu
  olinox14 a4875ad37b update symfony 10 miesięcy temu
  olinox14 4aa81d370c add basic entities 10 miesięcy temu
  olinox14 93482e6164 fix caddy config 10 miesięcy temu

+ 2 - 32
README.md

@@ -58,37 +58,7 @@ Start the nuxt server:
 > Without it, the frontend's requests will be blocked as CORS errors.
 
 
-## Add Fixtures
-
-    docker exec -it snc_demo_db bash
-
-    mysql -u root -p'Hxb3aMXUPb3m%$Ai*' -D snc_demo -e "
-        ALTER TABLE `snc_demo`.`author` AUTO_INCREMENT = 1;
-        INSERT INTO `snc_demo`.`author` (`name`)
-        VALUES 
-            ('Artist 1'),
-            ('Artist 2'),
-            ('Artist 3');
-    
-        ALTER TABLE `snc_demo`.`song` AUTO_INCREMENT = 1;
-        INSERT INTO `snc_demo`.`song` (`title`, `author_id`)
-        VALUES
-            ('Song A', 1),
-            ('Song B', 1),
-            ('Song C', 1),
-            ('Song D', 2),
-            ('Song E', 2),
-            ('Song F', 3),
-            ('Song G', 3);
-    "
-
-> Warning : If you plan to use this as a starter for your own project, don't forget to change the 
-> DB password !
-
-
-## Try it
-
-At this point, the application should be served at https://local.app.snc-demo.fr/.
+At this point, the application should be served at https://local.app.astra-corp.fr/.
 
 
 ## Accessing the OpenAPI Interface
@@ -96,7 +66,7 @@ At this point, the application should be served at https://local.app.snc-demo.fr
 The OpenAPI interface is available at:
 
 ```
-https://local.api.snc-demo.net/api/docs
+https://local.api.astra-corp.net/api/docs
 ```
 
 This interface provides documentation for all available API routes.

+ 6 - 0
api/.env.test

@@ -0,0 +1,6 @@
+# define your env variables for the test env here
+KERNEL_CLASS='App\Kernel'
+APP_SECRET='$ecretf0rt3st'
+SYMFONY_DEPRECATIONS_HELPER=999999
+PANTHER_APP_ENV=panther
+PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots

+ 5 - 0
api/.gitignore

@@ -8,3 +8,8 @@
 /var/
 /vendor/
 ###< symfony/framework-bundle ###
+
+###> phpunit/phpunit ###
+/phpunit.xml
+.phpunit.result.cache
+###< phpunit/phpunit ###

+ 115 - 87
api/composer.json

@@ -1,90 +1,118 @@
 {
-    "type": "project",
-    "license": "proprietary",
-    "minimum-stability": "stable",
-    "prefer-stable": true,
-    "require": {
-        "php": ">=8.2",
-        "ext-ctype": "*",
-        "ext-iconv": "*",
-        "api-platform/core": "^4.0",
-        "doctrine/doctrine-bundle": "^2.13",
-        "doctrine/doctrine-migrations-bundle": "^3.4",
-        "doctrine/orm": "^3.3",
-        "nelmio/cors-bundle": "^2.2",
-        "phpdocumentor/reflection-docblock": "^5.3",
-        "phpstan/phpdoc-parser": "^1.16",
-        "symfony/asset": "7.2.*",
-        "symfony/console": "7.2.*",
-        "symfony/doctrine-messenger": "7.2.*",
-        "symfony/dotenv": "7.2.*",
-        "symfony/error-handler": "7.2.*",
-        "symfony/expression-language": "7.2.*",
-        "symfony/flex": "^1.3.1",
-        "symfony/framework-bundle": "7.2.*",
-        "symfony/http-client": "7.2.*",
-        "symfony/intl": "7.2.*",
-        "symfony/lock": "7.2.*",
-        "symfony/mailer": "7.2.*",
-        "symfony/mercure": "^0.6.1",
-        "symfony/mercure-bundle": "^0.3.4",
-        "symfony/messenger": "7.2.*",
-        "symfony/monolog-bundle": "^3.7",
-        "symfony/property-access": "7.2.*",
-        "symfony/property-info": "7.2.*",
-        "symfony/runtime": "7.2.*",
-        "symfony/security-bundle": "7.2.*",
-        "symfony/serializer": "7.2.*",
-        "symfony/translation": "7.2.*",
-        "symfony/twig-bundle": "7.2.*",
-        "symfony/uid": "7.2.*",
-        "symfony/validator": "7.2.*",
-        "symfony/yaml": "7.2.*"
-    },
-    "config": {
-        "allow-plugins": {
-            "composer/package-versions-deprecated": true,
-            "symfony/flex": true,
-            "symfony/runtime": true
-        },
-        "optimize-autoloader": true,
-        "preferred-install": {
-            "*": "dist"
-        },
-        "sort-packages": true
-    },
-    "autoload": {
-        "psr-4": {
-            "App\\": "src/"
-        }
-    },
-    "replace": {
-        "symfony/polyfill-ctype": "*",
-        "symfony/polyfill-iconv": "*",
-        "symfony/polyfill-php72": "*",
-        "symfony/polyfill-php73": "*",
-        "symfony/polyfill-php74": "*",
-        "symfony/polyfill-php80": "*"
-    },
-    "scripts": {
-        "auto-scripts": {
-            "cache:clear": "symfony-cmd",
-            "assets:install %PUBLIC_DIR%": "symfony-cmd"
-        },
-        "post-install-cmd": [
-            "@auto-scripts"
-        ],
-        "post-update-cmd": [
-            "@auto-scripts"
-        ]
-    },
-    "conflict": {
-        "symfony/symfony": "*"
-    },
-    "extra": {
-        "symfony": {
-            "allow-contrib": false,
-            "require": "7.2.*"
-        }
+  "type": "project",
+  "license": "proprietary",
+  "minimum-stability": "stable",
+  "prefer-stable": true,
+  "require": {
+      "php": ">=8.2",
+      "ext-ctype": "*",
+      "ext-iconv": "*",
+      "api-platform/core": "^4.1",
+      "doctrine/dbal": "^3.9",
+      "doctrine/doctrine-bundle": "^2.13",
+      "doctrine/doctrine-migrations-bundle": "^3.4",
+      "doctrine/orm": "^3.3",
+      "nelmio/cors-bundle": "^2.2",
+      "phpdocumentor/reflection-docblock": "^5.3",
+      "phpstan/phpdoc-parser": "^1.16",
+      "symfony/asset": "7.3.*",
+      "symfony/console": "7.3.*",
+      "symfony/doctrine-messenger": "7.3.*",
+      "symfony/dotenv": "7.3.*",
+      "symfony/error-handler": "7.3.*",
+      "symfony/expression-language": "7.3.*",
+      "symfony/flex": "^1.3.1",
+      "symfony/framework-bundle": "7.3.*",
+      "symfony/http-client": "7.3.*",
+      "symfony/intl": "7.3.*",
+      "symfony/lock": "7.3.*",
+      "symfony/mailer": "7.3.*",
+      "symfony/mercure": "^0.6.1",
+      "symfony/mercure-bundle": "^0.3.4",
+      "symfony/messenger": "7.3.*",
+      "symfony/monolog-bundle": "^3.7",
+      "symfony/property-access": "7.3.*",
+      "symfony/property-info": "7.3.*",
+      "symfony/runtime": "7.3.*",
+      "symfony/security-bundle": "7.3.*",
+      "symfony/serializer": "7.3.*",
+      "symfony/translation": "7.3.*",
+      "symfony/twig-bundle": "7.3.*",
+      "symfony/uid": "7.3.*",
+      "symfony/validator": "7.3.*",
+      "symfony/yaml": "7.3.*"
+  },
+  "require-dev": {
+    "phpstan/extension-installer": "^1.2",
+    "phpstan/phpstan": "^2.1",
+    "phpstan/phpstan-deprecation-rules": "^2.0",
+    "phpstan/phpstan-doctrine": "^2.0",
+    "phpstan/phpstan-phpunit": "^2.0",
+    "phpstan/phpstan-symfony": "^2.0",
+    "phpunit/phpunit": "^9.6",
+    "symfony/browser-kit": "7.3.*",
+    "symfony/css-selector": "7.3.*",
+    "symfony/debug-bundle": "7.3.*",
+    "symfony/maker-bundle": "^1.48",
+    "symfony/phpunit-bridge": "7.3.*",
+    "symfony/stopwatch": "7.3.*",
+    "symfony/web-profiler-bundle": "7.3.*"
+  },
+  "config": {
+      "allow-plugins": {
+          "composer/package-versions-deprecated": true,
+          "symfony/flex": true,
+          "symfony/runtime": true,
+          "phpstan/extension-installer": true
+      },
+      "optimize-autoloader": true,
+      "preferred-install": {
+          "*": "dist"
+      },
+      "sort-packages": true
+  },
+  "autoload": {
+      "psr-4": {
+          "App\\": "src/"
+      }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "App\\Tests\\": "tests/"
     }
+  },
+  "replace": {
+      "symfony/polyfill-ctype": "*",
+      "symfony/polyfill-iconv": "*",
+      "symfony/polyfill-php72": "*",
+      "symfony/polyfill-php73": "*",
+      "symfony/polyfill-php74": "*",
+      "symfony/polyfill-php80": "*"
+  },
+  "scripts": {
+      "auto-scripts": {
+          "cache:clear": "symfony-cmd",
+          "assets:install %PUBLIC_DIR%": "symfony-cmd"
+      },
+      "post-install-cmd": [
+          "@auto-scripts"
+      ],
+      "post-update-cmd": [
+          "@auto-scripts"
+      ]
+  },
+  "conflict": {
+      "symfony/symfony": "*"
+  },
+  "extra": {
+      "symfony": {
+          "allow-contrib": false,
+          "require": "7.3.*"
+      }
+  },
+  "phpstan": {
+    "includes": [
+      "extension.neon"
+    ]
+  }
 }

Plik diff jest za duży
+ 199 - 274
api/composer.lock


+ 3 - 0
api/config/bundles.php

@@ -10,4 +10,7 @@ return [
     ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
     Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
     Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
+    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
+    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
+    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
 ];

+ 5 - 0
api/config/packages/debug.yaml

@@ -0,0 +1,5 @@
+when@dev:
+    debug:
+        # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
+        # See the "server:dump" command to start a new server.
+        dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

+ 3 - 0
api/config/packages/property_info.yaml

@@ -0,0 +1,3 @@
+framework:
+    property_info:
+        with_constructor_extractor: true

+ 13 - 0
api/config/packages/web_profiler.yaml

@@ -0,0 +1,13 @@
+when@dev:
+    web_profiler:
+        toolbar: true
+
+    framework:
+        profiler:
+            collect_serializer_data: true
+
+when@test:
+    framework:
+        profiler:
+            collect: false
+            collect_serializer_data: true

+ 8 - 0
api/config/routes/web_profiler.yaml

@@ -0,0 +1,8 @@
+when@dev:
+    web_profiler_wdt:
+        resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
+        prefix: /_wdt
+
+    web_profiler_profiler:
+        resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
+        prefix: /_profiler

+ 38 - 0
api/phpunit.xml.dist

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="tests/bootstrap.php"
+         convertDeprecationsToExceptions="false"
+>
+    <php>
+        <ini name="display_errors" value="1" />
+        <ini name="error_reporting" value="-1" />
+        <server name="APP_ENV" value="test" force="true" />
+        <server name="SHELL_VERBOSITY" value="-1" />
+        <server name="SYMFONY_PHPUNIT_REMOVE" value="" />
+        <server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Project Test Suite">
+            <directory>tests</directory>
+        </testsuite>
+    </testsuites>
+
+    <coverage processUncoveredFiles="true">
+        <include>
+            <directory suffix=".php">src</directory>
+        </include>
+    </coverage>
+
+    <listeners>
+        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
+    </listeners>
+
+    <extensions>
+    </extensions>
+</phpunit>

+ 0 - 67
api/src/Entity/Author.php

@@ -1,67 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Entity;
-
-use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
-use Doctrine\ORM\Mapping as ORM;
-use Doctrine\Common\Collections\Collection;
-
-#[ORM\Entity]
-#[ApiResource]
-class Author
-{
-    #[ORM\Id]
-    #[ORM\GeneratedValue]
-    #[ORM\Column]
-    private ?int $id = null;
-
-    #[ORM\Column(length: 100, nullable: false)]
-    private string $name;
-
-    #[ORM\OneToMany(targetEntity: Song::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
-    private Collection $songs;
-
-    public function getId(): ?int
-    {
-        return $this->id;
-    }
-
-    public function setId(?int $id): self
-    {
-        $this->id = $id;
-        return $this;
-    }
-
-    public function getName(): string
-    {
-        return $this->name;
-    }
-
-    public function setName(string $name): self
-    {
-        $this->name = $name;
-        return $this;
-    }
-
-    public function getSongs(): Collection
-    {
-        return $this->songs;
-    }
-
-    public function addSong(Song $songs): self
-    {
-        if (!$this->songs->contains($songs)) {
-            $this->songs[] = $songs;
-            $songs->setAuthor($this);
-        }
-
-        return $this;
-    }
-
-    public function removeSong(Song $songs): self {
-        $this->songs->removeElement($songs);
-        return $this;
-    }
-}

+ 0 - 60
api/src/Entity/Song.php

@@ -1,60 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Entity;
-
-use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\ApiFilter;
-use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
-use Doctrine\ORM\Mapping as ORM;
-use Symfony\Component\Serializer\Annotation\Groups;
-
-#[ORM\Entity]
-#[ApiResource]
-#[ApiFilter(SearchFilter::class, properties: ['author' => 'exact'])]
-class Song
-{
-    #[ORM\Id]
-    #[ORM\GeneratedValue]
-    #[ORM\Column]
-    private ?int $id = null;
-
-    #[ORM\Column(length: 100, nullable: false)]
-    private string $title;
-
-    #[ORM\ManyToOne(inversedBy: 'songs')]
-    private Author $author;
-
-    public function getId(): ?int
-    {
-        return $this->id;
-    }
-
-    public function setId(?int $id): self
-    {
-        $this->id = $id;
-        return $this;
-    }
-
-    public function getTitle(): string
-    {
-        return $this->title;
-    }
-
-    public function setTitle(string $title): self
-    {
-        $this->title = $title;
-        return $this;
-    }
-
-    public function getAuthor(): Author
-    {
-        return $this->author;
-    }
-
-    public function setAuthor(Author $author): self
-    {
-        $this->author = $author;
-        return $this;
-    }
-}

+ 74 - 0
api/src/Entity/User/Game.php

@@ -0,0 +1,74 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Entity\User;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[ORM\Entity]
+#[ApiResource]
+class Game
+{
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\Column(length: 100, unique: true, nullable: false)]
+    private string $name;
+
+    #[ORM\OneToMany(targetEntity: GameParticipation::class, mappedBy: 'game', cascade: ['persist', 'remove'])]
+    private Collection $gameParticipations;
+
+    public function __construct()
+    {
+        $this->gameParticipations = new ArrayCollection();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function setId(?int $id): self
+    {
+        $this->id = $id;
+        return $this;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setName(string $name): self
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    public function getGameParticipations(): Collection
+    {
+        return $this->gameParticipations;
+    }
+
+    public function addGameParticipation(GameParticipation $gameParticipation): self
+    {
+        if (!$this->gameParticipations->contains($gameParticipation)) {
+            $this->gameParticipations->add($gameParticipation);
+            $gameParticipation->setGame($this);
+        }
+
+        return $this;
+    }
+
+    public function removeGameParticipation(GameParticipation $gameParticipation): self
+    {
+        $this->gameParticipations->removeElement($gameParticipation);
+        return $this;
+    }
+}

+ 116 - 0
api/src/Entity/User/GameParticipation.php

@@ -0,0 +1,116 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Entity\User;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\DBAL\Types\Types;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ORM\Entity]
+#[ORM\Table(name: 'game_participations')]
+#[ORM\UniqueConstraint(name: 'unique_user_game', columns: ['user_id', 'game_id'])]
+#[ApiResource]
+class GameParticipation
+{
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'gameParticipations')]
+    #[ORM\JoinColumn(nullable: false)]
+    private User $user;
+
+    #[ORM\ManyToOne(targetEntity: Game::class, inversedBy: 'gameParticipations')]
+    #[ORM\JoinColumn(nullable: false)]
+    private Game $game;
+
+    #[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
+    private \DateTimeImmutable $joinedAt;
+
+    #[ORM\Column(length: 20, nullable: false, options: ['default' => 'active'])]
+    private string $status = 'active';
+
+    #[ORM\Column(nullable: true)]
+    private ?int $score = null;
+
+    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
+    private ?\DateTimeImmutable $lastPlayedAt = null;
+
+    public function __construct()
+    {
+        $this->joinedAt = new \DateTimeImmutable();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getUser(): User
+    {
+        return $this->user;
+    }
+
+    public function setUser(User $user): self
+    {
+        $this->user = $user;
+        return $this;
+    }
+
+    public function getGame(): Game
+    {
+        return $this->game;
+    }
+
+    public function setGame(Game $game): self
+    {
+        $this->game = $game;
+        return $this;
+    }
+
+    public function getJoinedAt(): \DateTimeImmutable
+    {
+        return $this->joinedAt;
+    }
+
+    public function setJoinedAt(\DateTimeImmutable $joinedAt): self
+    {
+        $this->joinedAt = $joinedAt;
+        return $this;
+    }
+
+    public function getStatus(): string
+    {
+        return $this->status;
+    }
+
+    public function setStatus(string $status): self
+    {
+        $this->status = $status;
+        return $this;
+    }
+
+    public function getScore(): ?int
+    {
+        return $this->score;
+    }
+
+    public function setScore(?int $score): self
+    {
+        $this->score = $score;
+        return $this;
+    }
+
+    public function getLastPlayedAt(): ?\DateTimeImmutable
+    {
+        return $this->lastPlayedAt;
+    }
+
+    public function setLastPlayedAt(?\DateTimeImmutable $lastPlayedAt): self
+    {
+        $this->lastPlayedAt = $lastPlayedAt;
+        return $this;
+    }
+}

+ 103 - 0
api/src/Entity/User/User.php

@@ -0,0 +1,103 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Entity\User;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[ORM\Entity]
+#[ApiResource]
+class User
+{
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\Column(length: 100, unique: true, nullable: false)]
+    private string $username;
+
+    #[ORM\Column(length: 255, unique: true, nullable: false)]
+    #[Assert\Email(message: 'invalid-email-format', mode: 'strict')]
+    private string $email;
+
+    #[ORM\Column(length: 100, nullable: false)]
+    private string $password;
+
+    #[ORM\OneToMany(mappedBy: 'user', targetEntity: GameParticipation::class, cascade: ['persist', 'remove'])]
+    private Collection $gameParticipations;
+
+    public function __construct()
+    {
+        $this->gameParticipations = new ArrayCollection();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function setId(?int $id): self
+    {
+        $this->id = $id;
+        return $this;
+    }
+
+    public function getUsername(): string
+    {
+        return $this->username;
+    }
+
+    public function setUsername(string $username): self
+    {
+        $this->username = $username;
+        return $this;
+    }
+
+    public function getEmail(): string
+    {
+        return $this->email;
+    }
+
+    public function setEmail(string $email): self
+    {
+        $this->email = $email;
+        return $this;
+    }
+
+    public function getPassword(): string
+    {
+        return $this->password;
+    }
+
+    public function setPassword(string $password): self
+    {
+        $this->password = $password;
+        return $this;
+    }
+
+    public function getGameParticipations(): Collection
+    {
+        return $this->gameParticipations;
+    }
+
+    public function addGameParticipation(GameParticipation $gameParticipation): self
+    {
+        if (!$this->gameParticipations->contains($gameParticipation)) {
+            $this->gameParticipations->add($gameParticipation);
+            $gameParticipation->setUser($this);
+        }
+
+        return $this;
+    }
+
+    public function removeGameParticipation(GameParticipation $gameParticipation): self
+    {
+        $this->gameParticipations->removeElement($gameParticipation);
+        return $this;
+    }
+}

+ 87 - 0
api/symfony.lock

@@ -13,6 +13,15 @@
             "src/ApiResource/.gitignore"
         ]
     },
+    "doctrine/deprecations": {
+        "version": "1.1",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "1.0",
+            "ref": "87424683adc81d7dc305eefec1fced883084aab9"
+        }
+    },
     "doctrine/doctrine-bundle": {
         "version": "2.13",
         "recipe": {
@@ -52,6 +61,29 @@
             "config/packages/nelmio_cors.yaml"
         ]
     },
+    "phpstan/phpstan": {
+        "version": "2.1",
+        "recipe": {
+            "repo": "github.com/symfony/recipes-contrib",
+            "branch": "main",
+            "version": "1.0",
+            "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
+        }
+    },
+    "phpunit/phpunit": {
+        "version": "9.6",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "9.6",
+            "ref": "6a9341aa97d441627f8bd424ae85dc04c944f8b4"
+        },
+        "files": [
+            ".env.test",
+            "phpunit.xml.dist",
+            "tests/bootstrap.php"
+        ]
+    },
     "symfony/console": {
         "version": "6.4",
         "recipe": {
@@ -64,6 +96,18 @@
             "bin/console"
         ]
     },
+    "symfony/debug-bundle": {
+        "version": "7.3",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "5.3",
+            "ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
+        },
+        "files": [
+            "config/packages/debug.yaml"
+        ]
+    },
     "symfony/flex": {
         "version": "1.21",
         "recipe": {
@@ -119,6 +163,15 @@
             "config/packages/mailer.yaml"
         ]
     },
+    "symfony/maker-bundle": {
+        "version": "1.64",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "1.0",
+            "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
+        }
+    },
     "symfony/mercure-bundle": {
         "version": "0.3",
         "recipe": {
@@ -155,6 +208,27 @@
             "config/packages/monolog.yaml"
         ]
     },
+    "symfony/phpunit-bridge": {
+        "version": "7.3",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "7.3",
+            "ref": "dc13fec96bd527bd399c3c01f0aab915c67fd544"
+        }
+    },
+    "symfony/property-info": {
+        "version": "7.3",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "7.3",
+            "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
+        },
+        "files": [
+            "config/packages/property_info.yaml"
+        ]
+    },
     "symfony/routing": {
         "version": "6.4",
         "recipe": {
@@ -230,5 +304,18 @@
         "files": [
             "config/packages/validator.yaml"
         ]
+    },
+    "symfony/web-profiler-bundle": {
+        "version": "7.3",
+        "recipe": {
+            "repo": "github.com/symfony/recipes",
+            "branch": "main",
+            "version": "7.3",
+            "ref": "a363460c1b0b4a4d0242f2ce1a843ca0f6ac9026"
+        },
+        "files": [
+            "config/packages/web_profiler.yaml",
+            "config/routes/web_profiler.yaml"
+        ]
     }
 }

+ 9 - 0
api/tests/bootstrap.php

@@ -0,0 +1,9 @@
+<?php
+
+use Symfony\Component\Dotenv\Dotenv;
+
+require dirname(__DIR__).'/vendor/autoload.php';
+
+if (method_exists(Dotenv::class, 'bootEnv')) {
+    (new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
+}

BIN
app/.yarn/install-state.gz


+ 5 - 0
app/nuxt.config.js

@@ -63,6 +63,11 @@ export default defineNuxtConfig({
     },
     server: {
       port: 443,
+      allowedHosts: [
+        'local.app.astra-corp.net',
+        'localhost',
+        '127.0.0.1'
+      ],
       hmr: {
         protocol: 'wss',
         port: 24678

+ 1 - 1
app/package.json

@@ -18,7 +18,7 @@
     "nuxt": "^3.17.5",
     "sass": "^1.69.5",
     "vite-plugin-vuetify": "^2.1.1",
-    "vuetify": "3.8.8"
+    "vuetify": "3.9.2"
   },
   "devDependencies": {
     "@typescript-eslint/eslint-plugin": "^8.22.0",

+ 5 - 5
app/yarn.lock

@@ -3230,7 +3230,7 @@ __metadata:
     vite-plugin-vuetify: "npm:^2.1.1"
     vitest: "npm:3.0.4"
     vue-jest: "npm:^3.0.7"
-    vuetify: "npm:3.8.8"
+    vuetify: "npm:3.9.2"
   languageName: unknown
   linkType: soft
 
@@ -10818,9 +10818,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"vuetify@npm:3.8.8":
-  version: 3.8.8
-  resolution: "vuetify@npm:3.8.8"
+"vuetify@npm:3.9.2":
+  version: 3.9.2
+  resolution: "vuetify@npm:3.9.2"
   peerDependencies:
     typescript: ">=4.7"
     vite-plugin-vuetify: ">=2.1.0"
@@ -10833,7 +10833,7 @@ __metadata:
       optional: true
     webpack-plugin-vuetify:
       optional: true
-  checksum: 10c0/28975423c10cac61712f0983b8017c4ad52a37682fb28405aa5efb307f634da80f442f696b6ce2dd08a51060d91dc3bd697b6e523c1212e04106c28879fa4bc9
+  checksum: 10c0/68f3b4cc9a44b86e205c67358780c654c81652215540859d5cc346ee02807652c19f26943001c88e7b3e9b88f075c28e5cdeade4d61fb4b60c281bb7e62c5373
   languageName: node
   linkType: hard
 

+ 2 - 2
docker/caddy/caddy/Caddyfile

@@ -18,7 +18,7 @@ local.api.astra-corp.net {
   }
 
   # Proxy everything else to the API
-  reverse_proxy http://snc_demo_api
+  reverse_proxy http://astracorp_api
 
   log {
     output file /var/log/api-access.log {
@@ -30,7 +30,7 @@ local.api.astra-corp.net {
 
 local.app.astra-corp.net {
   tls internal
-  reverse_proxy astracorp_app:3002
+  reverse_proxy astracorp_app:3000
   log {
     output file /var/log/app-access.log {
       roll_size 10mb

+ 86 - 72
specs/concept.md

@@ -1,112 +1,126 @@
 # Concept
 
-Jeu de gestion / colonisation, façon caesar 3 expansif ; 
-Les affrontements et pillages sont possibles, mais pas spécialement encouragés ;
-La puissance politique dépend du développement de la ou les colonies ; 
-Jeu orienté détente.
+Jeu de gestion / colonisation,  / commerce
 
-On pourrait faire une première version sur terre, avec des technologies présentes. Le scénario serait basé sur un monde 
-post-nucléaire, en 2043 par ex. Les humains émergent de leurs abris progressivement, l'hiver nucléaire est passé, 
-certaines zones correspondant aux anciennes grandes villes restent invivables et les anciennes frontières ont 
-cessé d'exister, mais des villes renaissent.
+Inspiration jeux web équivalents **mais** : 
 
-# Idées de noms
+* plus de liberté de colonisation / exploitation
+* plus de place au commerce
+* types de carrières (fédération, indépendant, syndicat, pirate...)
+* combats limités (jeu plus tranquille, pas de risque de se faire anéantir en une journée ou de se retrouver en proie à des attaques incessantes)
 
-* Spring 2100
-* New start
-* not the end
 
-## Monde
+## Univers
 
-Le monde correspond à la carte du monde actuelle, après la montée des eaux et les destructions nucléaires.
-La carte est divisée en régions, de tailles variables. Les régions ont des biomes relativement homogènes
-et des ressources propres.
+Des secteurs spatiaux sont agencées autour du système de départ (solaire ou autre)
 
-Plusieurs villes peuvent être créées dans une même région, mais à une distance minimum les unes des autres.
+Les secteurs sont composés de systèmes.
+Chaque secteur, puis système, devient accessible lorsqu'un portail hyperspatial est construit.
 
-Les joueurs peuvent choisir une région de départ et la position de leur ville selon leur type de tribu.
+Au début du jeu, un secteur de départ non colonisable, et un nombre limité de secteurs de colonisation sont accessibles.
 
-Ils peuvent choisir s'ils jouent une tribu de survivants ou les habitants d'un abri.
+Les systèmes sont composés de :
 
-La tribu de survivants peut choisir l'emplacement de son installation, hors zones contaminées. Ils ont 
-un bonus d'exploration et de commerce.
+* planètes habitables (très rares)
+* planètes non-habitables mais terraformables (rares)
+* planètes non-terraformables (communes)
+* gazeuses (communes)
+* lunes (communes)
+* astéroïdes (communs)
+* phénomènes spéciaux (rares)
 
-Les habitants de l'abri démarrent dans des zones modérément contaminées et ont un bonus technologique.
+## Joueurs
 
-## Construction des colonies
+### Points
 
-### Principe général
+Un joueur gagne des points en développant des colonies, sa richesse et son influence.
 
-On part d'un bâtiment de départ : abri
+Le développement des colonies se mesure à la population et au développement de celle-ci.
+La richesse se mesure à la valeur des ressources disponibles.
+L'influence s'acquiert en investissant dans le secteur de départ.
 
-Une première zone est accessible
+### Carrières
 
-D'autres zones peuvent devenir accessibles. On peut les visiter dès le début, et les explorer lorsqu'on 
-développe les véhicules requis.
+Un joueur démarre en choisissant une carrière : 
 
-Les zones voisines deviennent constructibles lorsqu'on y a construit un terminal de "train".
+#### Membre d'une fédération
 
-On peut construire sur plusieurs niveaux (5m chacun?), pour commencer de -2 à +2 par exemple
-Les niveaux plus élevés ou plus profonds sont plus chers, et peuvent avoir des impacts sur la qualité de vie, comme 
-dans le cas d'une canalisation suspendue.
+Plus de facilité et de moyens au départ (argents, licences, technologies), 
+mais plus limité dans ses choix, et doit une part de ses revenus à sa fédération.
+Un membre de fédération pourra gagner en influence en grimpant les échelons au sein de celle ci.
 
-Les bâtiments ont des tailles et formes variables et à définir (tailles minimum et maxi cependant)
+#### Indépendant
 
-Le joueur peut définir des zones résidentielles, commerciales, résidentielles+commerciales ou industrielles.
-Il peut aussi tracer des routes.
+Plus de latitude dans ses choix, mais démarre sans l'appui technologique et financier d'une fédération. 
+Plus long à développer au départ.
+Peuvent se réunir en syndicats.
+Un indépendant pourra acquérir de l'influence en achetant par exemple des locaux sur la planète de départ (de 
+la boite aux lettres au gratte-ciel dans les quartiers d'affaires)
 
-La rapidité de construction ou de production dépend du développement industriel et des entreprises installées.
+#### Pirate
 
-L'efficacité d'un bâtiment dépend de sa capacité et du réseau de transport. Un bâtiment comme un hôpital par exemple,
-pourra desservir des habitations plus lointaines si un réseau de transport rapide est disponible entre les deux.
+(à venir)
 
-Les bâtiments résidentiels ont une valeur fiscale représentant l'attrait du lieu. Plus le lieu 
-est attrayant, plus la pression fiscale augmente, et plus les habitation s'élèvent et accueillent d'appartements.
+## Commerce
 
-La qualité d'un lieu de vie dépend des services et des biens disponibles par le commerce.
+### Ressources
 
-### Services
+De nombreux types de ressources sont exploitables à travers l'univers.
+Ces ressources servent de base au commerce, et permettent le développement des colonies, la recherche, et la production.
+Les ressources peuvent ensuite être transformées en produits à plus forte valeur ajoutée via la production des colonies.
 
-Les services publics sont :
+#### Exemples de ressources
 
-* santé: dispensaires, centres médicaux, hôpitaux
-* sécurité: commissariats de quartier, commissariats, casernes d'intervention d'urgence
-* secours: petit centre de secours, centre de secours
-* ramassage des déchets : centre de retraitement, points de dépot, décharges, incinérateurs
-* alimentation en eau : usine de dessalement, centrale hydroponique, centrale à hydrogène, station de traitement, postes de relevage, station de pompage
-* éducation : écoles, collèges, universités, bibliothèques
-* divertissement et culture : 
-* militaire : 
-* énergie et réseau : centrales diverses, armoires électriques, 
-* administration et impôts :
+##### Métaux et Minéraux
+
+* Fer : Abondant dans les astéroïdes de type M, utilisé pour la construction de structures spatiales.
+* Nickel : Souvent associé au fer dans les astéroïdes, essentiel pour les alliages résistants.
+* Cobalt : Utilisé dans les batteries et les alliages haute performance.
+* Platine et métaux du groupe du platine : Pour les catalyseurs, l’électronique et les systèmes de propulsion avancés.
+* Or : Pour les circuits électroniques et les revêtements réflecteurs.
+* Argent : Utilisé en électronique et pour ses propriétés antibactériennes.
+* Terres rares (néodyme, dysprosium, etc.) : Critiques pour les aimants, les lasers et les technologies vertes.
+* Aluminium : Léger et résistant, idéal pour la construction de vaisseaux et habitats.
+* Titane : Résistant à la corrosion, utilisé dans les structures spatiales.
+* Silice et silicates : Pour la production de verre, de céramiques et de panneaux solaires.
+
+##### Gaz et Composés Volatils
+
+* Hydrogène : Carburant pour les moteurs-fusées et la production d’énergie.
+* Hélium-3 : Présent sur la Lune, potentiellement utilisé pour la fusion nucléaire.
+* Eau (glace) : Source d’oxygène, d’hydrogène et d’eau potable, trouvée sur les comètes et les lunes glacées.
+* Méthane : Carburant ou matière première pour la synthèse de produits chimiques.
+* Ammoniac : Utilisé comme réfrigérant ou pour la production d’engrais.
+* Deutérium : Isotope de l’hydrogène, utilisé dans les réactions de fusion nucléaire.
+* Azote : Pour les atmosphères artificielles et la production d’engrais.
+* Oxygène : Essentiel pour la respiration et comme comburant pour les moteurs-fusées.
+
+##### Autres Ressources
+
+* Régolithe lunaire ou astéroïdal : Matériau brut pour la construction in situ (briques, béton lunaire).
+* Carbone : Sous forme de graphite ou de diamant, pour les matériaux structurels ou l’électronique.
+
+### Principe du commerce
+
+Une bourse galactique est accessible à tout moment, et permet de consulter les prix actuels des ressources.
+Ces prix varient d'heure en heure selon l'offre et la demande.
+Une demande de base (variable) est générée par le secteur de départ, le reste l'est par les joueurs eux-mêmes. 
+Les échanges commerciaux peuvent se faire de joueur à joueur (plus rentables), ou se faire sous forme d'achat/vente en gros (moins rentables).
+Tout échange requiert l'existence d'une route commerciale, la disponibilité de vaisseaux de commerce.
 
-## Commerce
 
-De nombreuses ressources sont disponibles et s'obtiennent par :
+## Colonies
 
-* la pêche
-* la chasse
-* l'agriculture
-* l'extraction
-* ...etc
+Une colonie se développe à partir d'un vaisseau de colonisation, qui devient ensuite le batiment principal de la colonie.
+Des bâtiments spécialisés peuvent ensuite être construits.
+Une population se développe ensuite en fonction de l'offre en ressources et de la présence de batiments spéciaux.
+La population pourra ensuite fournir des ressources supplémentaires (services, technologies, produits manufacturés...)
 
-Ces ressources peuvent ensuite être transformées pour obtenir une valeur ajoutée plus élevée.
-Elles peuvent être échangées grâce au commerce.
 
-Des ressources de toute sorte sont également accessibles par les expéditions d'exploration.
 
-Certaines ressources n'existent que dans des régions spécifiques, rendant le commerce nécessaire.
 
-Les zones commerciales rendent accessibles les biens aux habitants.
 
-## Factions
 
-Les joueurs sont encouragés à se rassembler en factions ou alliances.
-Des outils sont proposés pour faciliter le commerce, la communication et permettre de faire des choix quant
-au modèle politique de la faction. Est-ce une fédération dont les membres votent ou un système féodal où les plus puissants
-ont la priorité de choix, mais doivent protection à leurs sujets ?
 
 
-## Suivi historique
 
-Les évènements historiques sont tous enregistrés dans un journal (fondations de villes, création ou disparitions de factions...)

+ 0 - 61
specs/variant_space.md

@@ -1,61 +0,0 @@
-# Concept
-
-On pourrait faire une première version sur terre, avec des technologies présentes. Le scénario serait basé sur un monde 
-post-nucléaire, en 2043 par ex. Les humains émergent de leurs abris progressivement, l'hiver nucléaire est passé, 
-certaines zones correspondant aux anciennes grandes villes restent invivables et les anciennes frontières ont 
-cessé d'exister, mais des villes renaissent.
-
-# Idées de noms
-
-Avec un scénario conquête spatiale :
-
-* constellation
-* new stars
-* new worlds
-* far space
-
-## Factions
-
-Fédération : la plus puissante et répandue, mais avec plus de réglementation et de limites politiques
-Empire : pendant totalitaire de la fédération, tyrannique et guerrier
-Hyperspace Inc. : entreprise privée, orientée rentabilité, plus de souplesse mais tout de même soumise aux réglementations
-Pirates : pirates de l'espace, liberté complète, mais combattue par tous
-
-Les factions donnent des missions et des instructions
-Elles fournissent aussi un soutien militaire, en échange de points d'influence
-
-## Zones d'installation spatiales
-
-Une zone de l'espace devient accessible dès lors qu'un joueur a construit un portail spatial dans le secteur.
-Les zones les moins fréquentées sont plus favorables aux pirates.
-
-## Planètes
-
-Différents types de planètes colonisables :
-
-* habitables
-* non-habitables mais terraformables
-* rocheuses non-terraformables (taille, température...)
-* gazeuses
-* lunes
-
-On peut envisager un coefficient d'habitabilité global.
-
-## Construction des colonies
-
-### Principe général
-
-On part d'un module de départ (vaisseau)
-
-On peut construire sur plusieurs niveaux (5m chacun?), pour commencer de -2 à +2 par exemple
-Les niveaux plus élevés ou plus profonds sont plus chers, et peuvent avoir des impacts sur la qualité de vie, comme 
-dans le cas d'une canalisation suspendue.
-
-## Galaxie
-
-Les seuls systèmes non colonisables sont le système solaire et ses plus proches voisins.
-Les factions démarrent avec quelques milliers de systèmes (pas forcément voisins) dans leur territoire. 
-Ces systèmes abritent différents types de planètes colonisables de différents types.
-
-Quand un nouveau joueur rejoint une faction, il peut choisir le type de planète à coloniser, selon ce qui est disponible
-dans l'espace de la faction choisie.

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików