5 커밋 9af2810be3 ... d5ff58e9d4

작성자 SHA1 메시지 날짜
  olinox14 d5ff58e9d4 add concept.md 2 달 전
  olinox14 e68572b2e7 update vuetify and fix config 2 달 전
  olinox14 a4875ad37b update symfony 2 달 전
  olinox14 4aa81d370c add basic entities 2 달 전
  olinox14 93482e6164 fix caddy config 2 달 전

+ 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"
+    ]
+  }
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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.

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.