feat: Initialisation du projet mainframe
Ajout de controllers, services, assets et configuration initiale.
This commit is contained in:
4
.env
4
.env
@@ -26,7 +26,7 @@ APP_SECRET=
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
DATABASE_URL="postgresql://symfony_user:ChangeMeInProd!@db:5432/app_db?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
@@ -47,3 +47,5 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###> sentry/sentry-symfony ###
|
||||
SENTRY_DSN=
|
||||
###< sentry/sentry-symfony ###
|
||||
VITE_LOAD=0
|
||||
REDIS_DSN="redis://redis:6379"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import './app.scss'
|
||||
|
||||
2
assets/app.scss
Normal file
2
assets/app.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss";
|
||||
@import url('https://fonts.googleapis.com/css2?family=DynaPuff:wght@400..700&display=swap');
|
||||
@@ -1,19 +1,19 @@
|
||||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
# Nom unique de votre application : utilisé pour calculer des espaces de noms stables pour les clés de cache.
|
||||
# Ceci est CRUCIAL pour éviter les collisions de clés si plusieurs applications partagent le même serveur de cache (ex: Redis).
|
||||
# Décommentez et remplacez par une valeur unique à votre projet (ex: "mon_entreprise/mon_app")
|
||||
prefix_seed: 'e-cosplay/contest' # <-- REMPLACEZ CECI PAR UN NOM UNIQUE À VOTRE PROJET
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
||||
# En production, utilisez un adaptateur de cache rapide et performant comme Redis.
|
||||
# Assurez-vous que votre serveur Redis est accessible.
|
||||
app: cache.adapter.redis
|
||||
default_redis_provider: '%env(REDIS_DSN)%'
|
||||
# Vous pouvez également optimiser les pools personnalisés pour la production si besoin.
|
||||
pools:
|
||||
my.user_data_cache:
|
||||
adapter: cache.adapter.redis
|
||||
my.api_data_cache:
|
||||
adapter: cache.adapter.redis
|
||||
vite_cache_pool:
|
||||
adapter: cache.adapter.redis
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
intercept_redirects: false
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
controllers:
|
||||
|
||||
resource:
|
||||
path: ../src/Controller/
|
||||
namespace: App\Controller
|
||||
type: attribute
|
||||
|
||||
|
||||
|
||||
presta_sitemap:
|
||||
resource: "@PrestaSitemapBundle/config/routing.yml"
|
||||
|
||||
@@ -16,5 +16,9 @@ services:
|
||||
App\:
|
||||
resource: '../src/'
|
||||
|
||||
App\Twig\ViteAssetExtension:
|
||||
arguments:
|
||||
$manifest: '%kernel.project_dir%/public/build/.vite/manifest.json'
|
||||
$cache: '@vite_cache_pool'
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
@@ -10,7 +10,7 @@ services:
|
||||
# Utilise l'UID/GID de l'hôte pour éviter les problèmes de permissions
|
||||
UID: ${UID:-1000}
|
||||
GID: ${GID:-1000}
|
||||
container_name: ecosplay_php
|
||||
container_name: mainframe_php
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- XDEBUG_MODE=coverage
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
args:
|
||||
UID: ${UID:-1000}
|
||||
GID: ${GID:-1000}
|
||||
container_name: ecosplay_messenger_worker
|
||||
container_name: mainframe_messenger_worker
|
||||
restart: unless-stopped
|
||||
# Commande pour lancer le worker. 'async' est le nom du transport par défaut.
|
||||
command: php bin/console messenger:consume async --memory-limit=128M --time-limit=3600
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
# Conteneur pour compiler les assets JS/CSS en développement
|
||||
bun:
|
||||
image: oven/bun:1-slim
|
||||
container_name: ecosplay_bun
|
||||
container_name: mainframe_bun
|
||||
restart: unless-stopped
|
||||
# Exécute les commandes avec l'utilisateur de l'hôte pour éviter les problèmes de permissions sur node_modules
|
||||
user: "${UID:-1000}:${GID:-1000}"
|
||||
@@ -74,7 +74,7 @@ services:
|
||||
# Serveur web moderne qui sert l'application et gère le PHP-FPM
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: ecosplay_caddy
|
||||
container_name: mainframe_caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Mappe le port 8000 de l'hôte au port 80 du conteneur
|
||||
@@ -92,7 +92,7 @@ services:
|
||||
# --- Service Base de Données principale (PostgreSQL) ---
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: ecosplay_db
|
||||
container_name: mainframe_db
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5432:5432"
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# --- Service Cache/Messenger (Redis) ---
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: ecosplay_redis
|
||||
container_name: mainframe_redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mainframe_network # Assignation au réseau commun
|
||||
@@ -118,7 +118,7 @@ services:
|
||||
# Intercepte tous les emails envoyés en développement
|
||||
mailhog:
|
||||
image: mailhog/mailhog:latest
|
||||
container_name: ecosplay_mailhog
|
||||
container_name: mainframe_mailhog
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Port 1025 pour le serveur SMTP factice
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
# Fournit une API compatible S3 pour le stockage de fichiers
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-02-03T21-03-04Z
|
||||
container_name: ecosplay_minio
|
||||
container_name: mainframe_minio
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Port 9000 pour l'API S3
|
||||
@@ -153,7 +153,7 @@ services:
|
||||
# --- Service de Gestion des Secrets (HashiCorp Vault) ---
|
||||
vault:
|
||||
image: hashicorp/vault:latest
|
||||
container_name: ecosplay_vault
|
||||
container_name: mainframe_vault
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8210:8200" # Mappe le port 8210 de l'hôte au port 8200 du conteneur Vault
|
||||
|
||||
18
src/Controller/Artemis/SecurityController.php
Normal file
18
src/Controller/Artemis/SecurityController.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis;
|
||||
|
||||
use App\Attribute\Mainframe;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis',name: 'login',methods: 'GET')]
|
||||
#[Mainframe(index: false,sitemap: false,sitemapPage: null)]
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
return$this->json([]);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/',name: 'toot',methods: 'GET')]
|
||||
#[Mainframe(index: false,sitemap: false,sitemapPage: null)]
|
||||
#[Route(path: '/',name: 'root',methods: 'GET')]
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
return$this->json([]);
|
||||
|
||||
22
src/EventListener/SitemapSubscriber.php
Normal file
22
src/EventListener/SitemapSubscriber.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
|
||||
use Presta\SitemapBundle\Service\UrlContainerInterface;
|
||||
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
|
||||
|
||||
class SitemapSubscriber implements EventSubscriberInterface {
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
SitemapPopulateEvent::class => 'populate',
|
||||
];
|
||||
}
|
||||
public function populate(SitemapPopulateEvent $event): void
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
86
src/Twig/ViteAssetExtension.php
Normal file
86
src/Twig/ViteAssetExtension.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class ViteAssetExtension extends AbstractExtension
|
||||
{
|
||||
private ?array $manifestData = null;
|
||||
|
||||
const CACHE_KEY = 'vite_manifest';
|
||||
|
||||
private readonly bool $isDev;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $manifest,
|
||||
private readonly CacheItemPoolInterface $cache,
|
||||
) {
|
||||
$this->isDev = $_ENV['VITE_LOAD'] == "0";
|
||||
}
|
||||
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('vite_asset', $this->asset(...), ['is_safe' => ['html']])
|
||||
];
|
||||
}
|
||||
|
||||
public function asset(string $entry, array $deps): string
|
||||
{
|
||||
if ($this->isDev) {
|
||||
return $this->assetDev($entry, $deps);
|
||||
}
|
||||
|
||||
return $this->assetProd($entry);
|
||||
}
|
||||
|
||||
public function assetDev(string $entry, array $deps): string
|
||||
{
|
||||
$html = <<<HTML
|
||||
<script type="module" src="http://localhost:5173/assets/@vite/client"></script>
|
||||
HTML;
|
||||
return $html . <<<HTML
|
||||
<script type="module" src="http://localhost:5173/assets/{$entry}" defer></script>
|
||||
HTML;
|
||||
}
|
||||
|
||||
public function assetProd(string $entry): string
|
||||
{
|
||||
|
||||
if ($this->manifestData === null) {
|
||||
$item = $this->cache->getItem(self::CACHE_KEY);
|
||||
if ($item->isHit()) {
|
||||
$this->manifestData = $item->get();
|
||||
} else {
|
||||
$this->manifestData = json_decode((string)file_get_contents($this->manifest), true);
|
||||
$item->set($this->manifestData);
|
||||
$this->cache->save($item);
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->manifestData[$entry]['file'] ?? '';
|
||||
$css = $this->manifestData[$entry]['css'] ?? [];
|
||||
$imports = $this->manifestData[$entry]['imports'] ?? [];
|
||||
$html = <<<HTML
|
||||
<script type="module" src="/build/{$file}" defer></script>
|
||||
HTML;
|
||||
foreach($css as $cssFile) {
|
||||
$html .= <<<HTML
|
||||
<link rel="stylesheet" media="screen" href="/build/{$cssFile}"/>
|
||||
HTML;
|
||||
}
|
||||
|
||||
foreach($imports as $import) {
|
||||
$html .= <<<HTML
|
||||
<link rel="modulepreload" href="/assets/{$import}"/>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
23
tests/Controller/Artemis/SecurityControllerTest.php
Normal file
23
tests/Controller/Artemis/SecurityControllerTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller\Artemis;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class SecurityControllerTest extends WebTestCase
|
||||
{
|
||||
public function testLogin(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
// Request a specific page
|
||||
$crawler = $client->request('GET', '/artemis');
|
||||
|
||||
// Validate a successful response and some content
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertResponseHeaderSame('Content-Type', 'application/json');
|
||||
|
||||
}
|
||||
}
|
||||
55
tests/EventListener/SitemapSubscriberTest.php
Normal file
55
tests/EventListener/SitemapSubscriberTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\EventListener;
|
||||
|
||||
use App\EventListener\SitemapSubscriber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
|
||||
use Presta\SitemapBundle\Service\UrlContainerInterface;
|
||||
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
|
||||
|
||||
class SitemapSubscriberTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var UrlGeneratorInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $urlGenerator;
|
||||
|
||||
/**
|
||||
* @var UrlContainerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $urlContainer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Mock the UrlGeneratorInterface
|
||||
$this->urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
|
||||
// Mock the UrlContainerInterface
|
||||
$this->urlContainer = $this->createMock(UrlContainerInterface::class);
|
||||
}
|
||||
|
||||
public function testGetSubscribedEvents(): void
|
||||
{
|
||||
// Assert that the getSubscribedEvents method returns the correct event.
|
||||
$expectedEvents = [
|
||||
SitemapPopulateEvent::class => 'populate',
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedEvents, SitemapSubscriber::getSubscribedEvents());
|
||||
}
|
||||
|
||||
public function testPopulate(): void
|
||||
{
|
||||
// Create an instance of the subscriber
|
||||
$subscriber = new SitemapSubscriber();
|
||||
|
||||
// Create a mock for SitemapPopulateEvent
|
||||
$event = new SitemapPopulateEvent($this->urlContainer, $this->urlGenerator);
|
||||
|
||||
// Call the populate method
|
||||
$subscriber->populate($event);
|
||||
$this->assertEquals("a","a");
|
||||
}
|
||||
}
|
||||
95
tests/Twig/ViteAssetExtensionTest.php
Normal file
95
tests/Twig/ViteAssetExtensionTest.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Twig;
|
||||
|
||||
use App\Twig\ViteAssetExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class ViteAssetExtensionTest extends TestCase
|
||||
{
|
||||
public function testAssetDev()
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = "0";
|
||||
|
||||
$cacheMock = $this->createMock(CacheItemPoolInterface::class);
|
||||
$extension = new ViteAssetExtension('/path/to/manifest.json', $cacheMock);
|
||||
|
||||
$output = $extension->asset('main.js', []);
|
||||
|
||||
$this->assertStringContainsString('<script type="module" src="http://localhost:5173/assets/@vite/client"></script>', $output);
|
||||
$this->assertStringContainsString('<script type="module" src="http://localhost:5173/assets/main.js" defer></script>', $output);
|
||||
}
|
||||
|
||||
public function testAssetProdWithCacheHit()
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = "1";
|
||||
|
||||
$manifest = [
|
||||
'main.js' => [
|
||||
'file' => 'main.123abc.js',
|
||||
'css' => ['style.456def.css'],
|
||||
'imports' => ['vendor.789ghi.js'],
|
||||
]
|
||||
];
|
||||
|
||||
$cacheItem = $this->createMock(CacheItemInterface::class);
|
||||
$cacheItem->method('isHit')->willReturn(true);
|
||||
$cacheItem->method('get')->willReturn($manifest);
|
||||
|
||||
$cacheMock = $this->createMock(CacheItemPoolInterface::class);
|
||||
$cacheMock->method('getItem')->willReturn($cacheItem);
|
||||
|
||||
$extension = new ViteAssetExtension('/unused/path.json', $cacheMock);
|
||||
$output = $extension->asset('main.js', []);
|
||||
|
||||
$this->assertStringContainsString('<script type="module" src="/build/main.123abc.js" defer></script>', $output);
|
||||
$this->assertStringContainsString('<link rel="stylesheet" media="screen" href="/build/style.456def.css"/>', $output);
|
||||
$this->assertStringContainsString('<link rel="modulepreload" href="/assets/vendor.789ghi.js"/>', $output);
|
||||
}
|
||||
|
||||
public function testAssetProdWithCacheMiss()
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = "1";
|
||||
|
||||
$manifestPath = tempnam(sys_get_temp_dir(), 'manifest');
|
||||
file_put_contents($manifestPath, json_encode([
|
||||
'main.js' => [
|
||||
'file' => 'main.abc.js',
|
||||
'css' => ['style.css'],
|
||||
'imports' => ['chunk.js'],
|
||||
]
|
||||
]));
|
||||
|
||||
$cacheItem = $this->createMock(CacheItemInterface::class);
|
||||
$cacheItem->method('isHit')->willReturn(false);
|
||||
$cacheItem->expects($this->once())->method('set');
|
||||
|
||||
$cacheMock = $this->createMock(CacheItemPoolInterface::class);
|
||||
$cacheMock->method('getItem')->willReturn($cacheItem);
|
||||
$cacheMock->expects($this->once())->method('save');
|
||||
|
||||
$extension = new ViteAssetExtension($manifestPath, $cacheMock);
|
||||
$output = $extension->asset('main.js', []);
|
||||
|
||||
$this->assertStringContainsString('<script type="module" src="/build/main.abc.js" defer></script>', $output);
|
||||
$this->assertStringContainsString('<link rel="stylesheet" media="screen" href="/build/style.css"/>', $output);
|
||||
$this->assertStringContainsString('<link rel="modulepreload" href="/assets/chunk.js"/>', $output);
|
||||
}
|
||||
public function testGetFunctions()
|
||||
{
|
||||
$_ENV['VITE_LOAD'] = "0";
|
||||
$cacheMock = $this->createMock(CacheItemPoolInterface::class);
|
||||
$extension = new ViteAssetExtension('/path/to/manifest.json', $cacheMock);
|
||||
|
||||
$functions = $extension->getFunctions();
|
||||
|
||||
$this->assertIsArray($functions);
|
||||
$this->assertNotEmpty($functions);
|
||||
$this->assertInstanceOf(TwigFunction::class, $functions[0]);
|
||||
$this->assertEquals('vite_asset', $functions[0]->getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export default defineConfig({
|
||||
|
||||
// Configuration CORS pour autoriser les requêtes depuis votre backend Symfony
|
||||
cors: {
|
||||
origin: ['http://esyweb.local']
|
||||
origin: ['https://esyweb.local']
|
||||
},
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
// Points d'entrée de votre application
|
||||
input: {
|
||||
administration: resolve(__dirname, 'assets/administration.js'),
|
||||
app: resolve(__dirname, 'assets/app.js'),
|
||||
// Exemple : 'styles': resolve(__dirname, 'assets/styles/main.scss'),
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user