diff --git a/composer.json b/composer.json index 9da9fe8..9776480 100644 --- a/composer.json +++ b/composer.json @@ -9,10 +9,12 @@ "php": ">=8.1", "ext-ctype": "*", "ext-iconv": "*", + "api-platform/core": "^3.2", "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.3", + "nelmio/cors-bundle": "^2.5", "phpdocumentor/reflection-docblock": "^5.6", "phpstan/phpdoc-parser": "^1.14", "symfony/asset": "6.1.*", diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml new file mode 100644 index 0000000..4d0c3f7 --- /dev/null +++ b/config/packages/api_platform.yaml @@ -0,0 +1,18 @@ +api_platform: + title: Hello API Platform + version: 1.0.0 + formats: + jsonld: ['application/ld+json'] + docs_formats: + jsonld: ['application/ld+json'] + jsonopenapi: ['application/vnd.openapi+json'] + html: ['text/html'] + defaults: + stateless: true + cache_headers: + vary: ['Content-Type', 'Authorization', 'Origin'] + extra_properties: + standard_put: true + rfc_7807_compliant_errors: true + event_listeners_backward_compatibility_layer: false + keep_legacy_inflector: false diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml new file mode 100644 index 0000000..c766508 --- /dev/null +++ b/config/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/': null diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..9ebc483 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -4,14 +4,30 @@ security: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - users_in_memory: { memory: null } + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: username firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true - provider: users_in_memory + provider: app_user_provider + custom_authenticator: App\Security\LoginFormAuthenticator + logout: + path: app_logout + target: app_login + entry_point: App\Security\LoginFormAuthenticator + + + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + always_remember_me: true # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall @@ -22,8 +38,13 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + - { path: ^/login, roles: PUBLIC_ACCESS } + - { path: ^/register, roles: PUBLIC_ACCESS } + - { path: ^/, roles: ROLE_USER } + + + + when@test: security: diff --git a/config/routes/api_platform.yaml b/config/routes/api_platform.yaml new file mode 100644 index 0000000..38f11cb --- /dev/null +++ b/config/routes/api_platform.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api diff --git a/migrations/Version20250529203532.php b/migrations/Version20250529203532.php deleted file mode 100644 index e68202b..0000000 --- a/migrations/Version20250529203532.php +++ /dev/null @@ -1,44 +0,0 @@ -addSql(<<<'SQL' - CREATE TABLE emoji (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, nom VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, force DOUBLE PRECISION NOT NULL, robustesse DOUBLE PRECISION NOT NULL, intelligence DOUBLE PRECISION NOT NULL, vitesse DOUBLE PRECISION NOT NULL, nb_combat_gagne INTEGER NOT NULL, rarete INTEGER NOT NULL) - SQL); - $this->addSql(<<<'SQL' - CREATE TABLE rarity (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(50) NOT NULL, drop_rate DOUBLE PRECISION NOT NULL) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_B7C0BE465E237E06 ON rarity (name) - SQL); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql(<<<'SQL' - DROP TABLE emoji - SQL); - $this->addSql(<<<'SQL' - DROP TABLE rarity - SQL); - } -} diff --git a/public/css/home.css b/public/css/home.css index d8e65e7..f0e0550 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -11,12 +11,45 @@ h1 { color: #f8b435; } -.emoji-container { +.section-divider { + border: none; + border-top: 3px solid #f8b435; + margin: 50px auto 20px; + width: 80%; +} + +.section-break-icon { + text-align: center; + font-size: 2rem; + margin: -20px 0; + color: #f8b435; +} + +.section-title { + margin: 40px 10px; + font-size: 1.8rem; + color: #f8f5e0; +} + +.emoji-container:not(.base) { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 30px; + width: fit-content; + margin: 0 auto; +} + +.emoji-container.base { display: flex; justify-content: center; flex-wrap: wrap; gap: 30px; - margin-bottom: 10rem; + + border: 3px dashed #f8b435; + padding: 20px; + margin: 0 auto 30px auto; + border-radius: 15px; + width: fit-content; } .emoji-card { @@ -43,7 +76,7 @@ h1 { /* Animation brillance pour rareté légendaire */ @keyframes shine { 0% { background-position: 0px; } - 100% { background-position: 177px; } + 100% { background-position: 168px; } } .emoji-card.gold { @@ -54,17 +87,17 @@ h1 { .emoji-card.gold::before { content: ''; position: absolute; - top: 0; + top: 1%; left: 1%; - width: 98%; - height: 100%; + width: 97%; + height: 99%; background: linear-gradient( 120deg, rgba(255, 255, 255, 0) 0%, - rgba(255, 255, 255, 0.5) 50%, + rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100% ); - animation: shine 5s infinite; + animation: shine 3.5s infinite; pointer-events: none; z-index: -1; } @@ -219,7 +252,7 @@ color: #555; #selection-status { font-size: 1.1rem; - margin-bottom: 20px; + margin-top: 4rem; font-weight: bold; color: #f9e8c0; } @@ -245,4 +278,37 @@ color: #555; .vs-text { font-size: 1.5rem; font-weight: bold; +} + +/* Pagination */ + +.pagination-bar { + margin: 4rem 4rem; + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + color: #f8f5e0; + font-family: 'Georgia', serif; +} + +.pagination-bar button { + background: none; + border: none; + font-size: 1.1rem; + color: #f8f5e0; + cursor: pointer; + padding: 5px 10px; + border-radius: 50%; + transition: background 0.2s ease; +} + +.pagination-bar button.active { + background: #333; + color: white; + font-weight: bold; +} + +.pagination-bar button:hover:not(.active) { + color: #f8b435; } \ No newline at end of file diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..50e0556 --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,118 @@ +body { + background-color: #314e57; + font-family: 'Georgia', serif; + text-align: center; +} + +.login-container { + background-color: #3b6068; + border: 4px solid #000; + border-radius: 15px; + padding: 40px 30px; + margin: 60px auto; + width: 90%; + max-width: 400px; + box-shadow: 0 0 30px rgba(0,0,0,0.6); + color: #f8f5e0; + font-family: 'Georgia', serif; +} + +.login-title { + font-size: 2.5rem; + color: #f8b435; + margin-bottom: 20px; + text-shadow: 2px 2px 4px #000; +} + +.login-error { + background-color: #8b0000; + color: #fff8e1; + padding: 10px; + border: 2px solid #000; + border-radius: 8px; + margin-bottom: 20px; + font-weight: bold; +} + +.already-logged { + background-color: #f2e6c9; + color: #000; + padding: 12px; + border: 2px dashed #000; + border-radius: 8px; + margin-bottom: 20px; + font-size: 0.95rem; +} + +.logout-link { + display: inline-block; + margin-top: 10px; + color: #8b0000; + font-weight: bold; + text-decoration: underline; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.login-form label { + text-align: left; + color: #f8f5e0; + font-weight: bold; +} + +.login-form input[type="text"], +.login-form input[type="password"] { + padding: 10px; + border: 2px solid #000; + border-radius: 6px; + background-color: #f2e6c9; + font-family: 'Georgia', serif; + font-size: 1rem; + box-shadow: inset 2px 2px 5px rgba(0,0,0,0.2); +} + +.btn-login { + background-color: #f8b435; + color: #000; + font-weight: bold; + border: 3px solid #000; + padding: 10px; + border-radius: 8px; + font-size: 1.2rem; + cursor: pointer; + box-shadow: 3px 3px 0 #000; + transition: transform 0.2s ease; +} + +.btn-login:hover { + transform: scale(1.05); + background-color: #e09f30; +} + +.no-account { + margin-top: 30px; + color: #f8f5e0; +} + +.btn-register { + display: inline-block; + margin-top: 10px; + background-color: #f2e6c9; + color: #000; + border: 2px solid #000; + padding: 8px 20px; + border-radius: 6px; + text-decoration: none; + font-weight: bold; + box-shadow: 2px 2px 0 #000; + transition: transform 0.2s ease; +} + +.btn-register:hover { + transform: scale(1.05); + background-color: #e5d6b8; +} diff --git a/public/css/register.css b/public/css/register.css new file mode 100644 index 0000000..bb85486 --- /dev/null +++ b/public/css/register.css @@ -0,0 +1,88 @@ +body { + background-color: #314e57 !important; + font-family: 'Georgia', serif; + text-align: center; +} + +.register-container { + background-color: #3b6068; + border: 4px solid #000; + border-radius: 15px; + padding: 40px 30px; + margin: 60px auto; + width: 90%; + max-width: 400px; + box-shadow: 0 0 30px rgba(0,0,0,0.6); + color: #f8f5e0; + font-family: 'Georgia', serif; +} + +.register-title { + font-size: 2.5rem; + color: #f8b435; + margin-bottom: 20px; + text-shadow: 2px 2px 4px #000; +} + +.register-form label { + text-align: left; + color: #f8f5e0; + font-weight: bold; +} + +.register-form input[type="text"], +.register-form input[type="password"] { + padding: 10px; + border: 2px solid #000; + border-radius: 6px; + background-color: #f2e6c9; + font-family: 'Georgia', serif; + font-size: 1rem; + box-shadow: inset 2px 2px 5px rgba(0,0,0,0.2); + width: 100%; + margin-bottom: 15px; +} + +.btn-register-submit { + background-color: #f8b435; + color: #000; + font-weight: bold; + border: 3px solid #000; + padding: 10px; + border-radius: 8px; + font-size: 1.2rem; + cursor: pointer; + box-shadow: 3px 3px 0 #000; + transition: transform 0.2s ease; + width: 100%; + margin-bottom: 20px; +} + +.btn-register-submit:hover { + transform: scale(1.05); + background-color: #e09f30; +} + +.already-account { + color: #f8f5e0; + font-size: 0.95rem; +} + +.btn-login-link { + display: inline-block; + margin-top: 10px; + background-color: #f2e6c9; + color: #000; + border: 2px solid #000; + padding: 8px 20px; + border-radius: 6px; + text-decoration: none; + font-weight: bold; + box-shadow: 2px 2px 0 #000; + transition: transform 0.2s ease; +} + +.btn-login-link:hover { + transform: scale(1.05); + background-color: #e5d6b8; +} diff --git a/public/js/home.js b/public/js/home.js index d771b65..a9d7c12 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -42,22 +42,63 @@ document.addEventListener('DOMContentLoaded', () => { } } - function handleAction(type) { + async function handleAction(type) { if (selectedCards.length !== 2) { alert("Tu dois sélectionner 2 créatures."); return; } - const name1 = selectedCards[0].dataset.name; - const name2 = selectedCards[1].dataset.name; + const id1 = selectedCards[0].dataset.id; + const id2 = selectedCards[1].dataset.id; + + if (type === 'reproduction') { + try { + const response = await fetch(`/emojis/fusion/${encodeURIComponent(id1)}/${encodeURIComponent(id2)}`, { + method: 'POST', + headers: { + 'Accept': 'application/json' + } + }); + + if (!response.ok) { + throw new Error("Erreur serveur : " + response.status); + } + + const data = await response.json(); + + alert(`Succès : ${data.message} (ID : ${data.childId})`); + + // Tu peux aussi mettre à jour le DOM ici avec le nouvel enfant + // ex: ajouter une carte, etc. + } catch (error) { + console.error("Erreur lors de la reproduction :", error); + alert("Une erreur est survenue lors de la reproduction."); + } + + return; // on quitte ici + } if (type === 'combat') { - console.log(`Combat : ${name1} contre ${name2}`); - } else if (type === 'reproduction') { - console.log(`Accouplement : ${name1} et ${name2}`); + + fetch(`/emojis/fight/${id1}/${id2}`, { method: 'GET', + headers: { + 'Accept': 'application/json' + }}).then(response => { + if (!response.ok) { + throw new Error(`Erreur HTTP ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log('Données reçues :', data); + }) + .catch(error => { + console.error('Erreur lors de la requête :', error); + }); + } - // Réinitialiser après l'action + // Réinitialiser si pas redirection selectedCards.forEach(card => card.classList.remove('selected')); selectedCards = []; updateSelectionDisplay(); @@ -66,7 +107,9 @@ document.addEventListener('DOMContentLoaded', () => { // Ouvre / Ferme la popup d'information function togglePopup(id) { const popup = document.getElementById('popup-' + id); - popup.style.display = (popup.style.display === 'block') ? 'none' : 'block'; + if (popup) { + popup.style.display = (popup.style.display === 'block') ? 'none' : 'block'; + } } // Fermer les autres popups en cliquant ailleurs @@ -84,7 +127,7 @@ document.addEventListener('DOMContentLoaded', () => { const sortBy = document.getElementById('sort-select').value; const searchTerm = document.getElementById('search-input').value.toLowerCase(); - const cards = Array.from(document.querySelectorAll('.emoji-card')); + const cards = Array.from(document.querySelectorAll('#collection-container .emoji-card')); cards.forEach(card => { const color = card.dataset.color; @@ -101,7 +144,7 @@ document.addEventListener('DOMContentLoaded', () => { }); if (sortBy !== 'none') { - const container = document.querySelector('.emoji-container'); + const container = document.getElementById('collection-container'); // ✅ corrigé const visibleCards = cards.filter(c => c.style.display !== 'none'); visibleCards.sort((a, b) => { @@ -111,21 +154,29 @@ document.addEventListener('DOMContentLoaded', () => { }); visibleCards.forEach(card => container.appendChild(card)); + paginateCards(); } } // Appel Fonctionnalité de popup d'information document.querySelectorAll('.detail-icon').forEach(icon => { icon.addEventListener('click', (e) => { - const id = icon.parentElement.dataset.id; - togglePopup(id); - e.stopPropagation(); // empêche le clic d’aller à la carte + const card = icon.closest('.emoji-card'); + const id = card?.dataset.id; + if (id) { + togglePopup(id); + } + e.stopPropagation(); // évite que ça sélectionne la carte }); }); - // Appel Fonctionnalité de sélection des cartes - document.querySelectorAll('.emoji-card').forEach(card => { - card.addEventListener('click', () => toggleSelection(card)); + // Appliquer l'écouteur à toutes les cartes (base + collection) + document.querySelectorAll('.emoji-container').forEach(container => { + container.addEventListener('click', (e) => { + const card = e.target.closest('.emoji-card'); + if (!card || e.target.closest('.detail-icon')) return; // Ignore si clic sur icône ℹ️ + toggleSelection(card); + }); }); // Appel Fonctionnalité de combat et reproduction @@ -140,4 +191,66 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('search-input').addEventListener('input', applyFilters); document.getElementById('rarete-filter').addEventListener('change', applyFilters); document.getElementById('sort-select').addEventListener('change', applyFilters); + + /***** Partie Pagination ******/ + + const ITEMS_PER_PAGE = 10; + let currentPage = 1; + + function paginateCards() { + const allCards = Array.from(document.querySelectorAll('#collection-container .emoji-card')); + const container = document.getElementById('collection-container'); + const totalPages = Math.ceil(allCards.length / ITEMS_PER_PAGE); + + // Masquer toutes les cartes + allCards.forEach(card => card.style.display = 'none'); + + // Afficher uniquement les cartes de la page courante + const start = (currentPage - 1) * ITEMS_PER_PAGE; + const end = start + ITEMS_PER_PAGE; + + allCards.slice(start, end).forEach(card => card.style.display = 'block'); + + renderPagination(totalPages); + } + + function renderPagination(totalPages) { + const pagination = document.getElementById('pagination'); + pagination.innerHTML = ''; + + const addBtn = (text, page = null, isActive = false, disabled = false) => { + const btn = document.createElement('button'); + btn.textContent = text; + if (isActive) btn.classList.add('active'); + if (disabled) btn.disabled = true; + if (page !== null) { + btn.addEventListener('click', () => { + currentPage = page; + paginateCards(); + }); + } + pagination.appendChild(btn); + }; + + addBtn('Précédent', currentPage - 1, false, currentPage === 1); + + for (let i = 1; i <= totalPages; i++) { + if ( + i === 1 || + i === totalPages || + (i >= currentPage - 1 && i <= currentPage + 1) + ) { + addBtn(i, i, i === currentPage); + } else if ( + i === 2 && currentPage > 3 || + i === totalPages - 1 && currentPage < totalPages - 2 + ) { + addBtn('...'); + } + } + + addBtn('Suivant', currentPage + 1, false, currentPage === totalPages); + } + + paginateCards(); }); \ No newline at end of file diff --git a/src/ApiResource/.gitignore b/src/ApiResource/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Command/PopulateDBEmojiAvailableCommand.php b/src/Command/PopulateDBEmojiAvailableCommand.php new file mode 100644 index 0000000..0985b4c --- /dev/null +++ b/src/Command/PopulateDBEmojiAvailableCommand.php @@ -0,0 +1,78 @@ +connection = $connection; + } + + protected function configure() + { + $this + ->setDescription('Populate the database.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + + // On supprime la table si elle existe déjà + $this->connection->executeStatement('DROP TABLE IF EXISTS stock_emoji'); + + // On crée la table + $this->connection->executeStatement(' + CREATE TABLE stock_emoji ( + id INT PRIMARY KEY, + code VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL + ) + '); + + + // On peuple la table + $this->connection->executeStatement("INSERT INTO stock_emoji (id, code, name) VALUES + (1,'🤖','Rodolph'), + (2,'😺','Bobette'), + (3,'🧠','Diana'), + (4,'👻','Ian'), + (5,'🧟','Alice'), + (6,'🐶','Eric'), + (7,'👽','Hannah'), + (8,'🧛','Fiona'), + (9,'🎃','George'), + (10,'🐸','John'), + (11,'⚡','Charlie'), + (12,'💀','Benoit'), + (13,'🔥','Sophie'), + (14,'🧙','Bob'), + (15,'🌪️','Ethan'), + (16,'😎','Luna'), + (17,'😁','Maxence'), + (18,'🌟','Jasper'), + (19,'😈','Nora') + "); + + $output->writeln('Base de données peuplée.'); + } catch (\Exception $e) { + $output->writeln('Erreur : ' . $e->getMessage() . ''); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/Controller/EmojiController.php b/src/Controller/EmojiController.php index d931121..059ddb6 100644 --- a/src/Controller/EmojiController.php +++ b/src/Controller/EmojiController.php @@ -13,7 +13,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use App\Repository\EmojiRepository; -#[Route('/emoji', name: 'emoji')] +#[Route('/emojis', name: 'app_emoji_')] class EmojiController extends AbstractController { private RarityRepository $rarityRepository; @@ -25,15 +25,34 @@ class EmojiController extends AbstractController $this->httpClient = $httpClient; } - #[Route('/emoji', name: 'app_emoji')] + #[Route('/', name: 'emojis')] public function index(): Response { + $this->testToMove(); return $this->render('emoji/index.html.twig', [ 'controller_name' => 'EmojiController', ]); } - #[Route('/add/{code}', name: 'add')] + #[Route('/count', name: 'count')] + public function count(EmojiRepository $emojiRepository): Response + { + $count = count($emojiRepository->findAll()); + return new Response(['count' => $count]); + } + + #[Route('/addRarity', name: 'add_rarity')] + public function addRarityDebug(EntityManagerInterface $entityManager) { + $rarity = new Rarity(); + $rarity->setName('Bip'); + $rarity->setDropRate(42); + $entityManager->persist($rarity); + $entityManager->flush(); + + return new Response(); + } + + #[Route('/add/{code}', name: 'add_code')] public function addEmojiDebug(string $code, EntityManagerInterface $entityManager) { $emoji = new Emoji(); $emoji->setCode($code); @@ -168,9 +187,71 @@ class EmojiController extends AbstractController return new JsonResponse([ 'message' => 'Child created', - 'mommy' => $child->getParent1()->getCode(), - 'daddy' => $child->getParent2()->getCode(), - 'baby' => $child->getCode() + 'childId' => $child->getId() ]); } -} \ No newline at end of file + + + #[Route('/fight/{idEmoji1}/{idEmoji2}', name: 'fight_emoji')] + public function fightEmoji(int $idEmoji1, int $idEmoji2, EntityManagerInterface $entityManager, EmojiRepository $emojiRepository): JsonResponse { + $emoji1 = $emojiRepository->find($idEmoji1); + $emoji2 = $emojiRepository->find($idEmoji2); + + if (!$emoji1 || !$emoji2) { + return new JsonResponse(['error' => 'Emoji not found'], 404); + } + $aleatoire = random_int(0,3); + $valEmoji1 = [$emoji1->getStrength(),$emoji1->getToughness(),$emoji1->getIntelligence(),$emoji1->getSpeed()]; + $valEmoji2 = [$emoji2->getStrength(),$emoji2->getToughness(),$emoji2->getIntelligence(),$emoji2->getSpeed()]; + $difference = $valEmoji1[$aleatoire] - $valEmoji2[$aleatoire]; + // if($difference > 0){ + // $emoji1->wonFight(); + // $entityManager->persist($emoji1); + // $entityManager->remove($emoji2); + // } else { + // $emoji2->wonFight(); + // $entityManager->persist($emoji2); + // $entityManager->remove($emoji1); + // } + + // $entityManager->flush(); + + if ($difference > 0) { + $winner = $emoji1; + $loser = $emoji2; + $wonFight = 'left'; + } else { + $winner = $emoji2; + $loser = $emoji1; + $wonFight = 'right'; + } + + $winner->wonFight(); + $entityManager->persist($winner); + $entityManager->remove($loser); + $entityManager->flush(); + + return new JsonResponse([ + 'emoji1' => $emoji1->getCode(), + 'emoji2' => $emoji2->getCode(), + 'winner' => $wonFight + ]); + } + + public function testToMove(){ + $e = new Emoji(); + $e->setName("ROBERT"); + $e->setStrength(5); + $e->setIntelligence(2); + $e->setToughness(3); + $e->setSpeed(4); + $e2 = new Emoji(); + $e2->setName("BIBOP"); + $e2->setStrength(42); + $e2->setIntelligence(1); + $e2->setToughness(1); + $e2->setSpeed(1); + $vic = $this->fightEmoji($e,$e2); + echo $vic->getName(); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index f5df842..87f291d 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -8,10 +8,10 @@ use Symfony\Component\Routing\Annotation\Route; class HomeController extends AbstractController { - #[Route('/', name: 'home')] + #[Route('/', name: 'app_home')] public function index(): Response { - $emojis = [ + $emojisDeBase = [ [ 'id' => 1, 'nom' => 'Bob', @@ -21,7 +21,7 @@ class HomeController extends AbstractController 'intelligence' => 7.8, 'vitesse' => 10.0, 'nbCombatGagne' => 3, - 'rarete' => 2, // épique + 'rarete' => 3, // épique ], [ 'id' => 2, @@ -43,24 +43,169 @@ class HomeController extends AbstractController 'intelligence' => 18.0, 'vitesse' => 17.0, 'nbCombatGagne' => 10, - 'rarete' => 4, // légendaire + 'rarete' => 5, // légendaire + ], + [ + 'id' => 4, + 'nom' => 'Sophie', + 'code' => '😎', + 'force' => 8.0, + 'robustesse' => 7.5, + 'intelligence' => 9.0, + 'vitesse' => 8.5, + 'nbCombatGagne' => 2, + 'rarete' => 4, // mythique ] ]; - // Ajout de la couleur selon la rareté - foreach ($emojis as &$emoji) { + $emojisCrees = [ + [ + 'id' => 6, + 'nom' => 'Benoit', + 'code' => '🤖', + 'force' => 15.0, + 'robustesse' => 12.0, + 'intelligence' => 14.0, + 'vitesse' => 13.0, + 'nbCombatGagne' => 5, + 'rarete' => 3, // épique + ], + [ + 'id' => 7, + 'nom' => 'Eric', + 'code' => '🌟', + 'force' => 18.0, + 'robustesse' => 16.0, + 'intelligence' => 17.0, + 'vitesse' => 19.0, + 'nbCombatGagne' => 8, + 'rarete' => 5, // légendaire + ], + [ + 'id' => 5, + 'nom' => 'Alice', + 'code' => '🥳', + 'force' => 6.0, + 'robustesse' => 5.0, + 'intelligence' => 4.5, + 'vitesse' => 6.5, + 'nbCombatGagne' => 0, + 'rarete' => 2, // rare + ], + [ + 'id' => 8, + 'nom' => 'Bobette', + 'code' => '🤩', + 'force' => 10.0, + 'robustesse' => 9.0, + 'intelligence' => 11.0, + 'vitesse' => 12.0, + 'nbCombatGagne' => 4, + 'rarete' => 4, // mythique + ], + [ + 'id' => 9, + 'nom' => 'Charlie', + 'code' => '😇', + 'force' => 7.0, + 'robustesse' => 6.0, + 'intelligence' => 8.0, + 'vitesse' => 7.5, + 'nbCombatGagne' => 2, + 'rarete' => 1, // commun + ], + [ + 'id' => 10, + 'nom' => 'Diana', + 'code' => '😈', + 'force' => 14.0, + 'robustesse' => 13.0, + 'intelligence' => 15.0, + 'vitesse' => 16.0, + 'nbCombatGagne' => 6, + 'rarete' => 4, // mythique + ], + [ + 'id' => 11, + 'nom' => 'Ethan', + 'code' => '🤯', + 'force' => 9.0, + 'robustesse' => 8.0, + 'intelligence' => 10.0, + 'vitesse' => 11.0, + 'nbCombatGagne' => 3, + 'rarete' => 3, // épique + ], + [ + 'id' => 12, + 'nom' => 'Fiona', + 'code' => '🥺', + 'force' => 4.0, + 'robustesse' => 3.5, + 'intelligence' => 5.0, + 'vitesse' => 4.5, + 'nbCombatGagne' => 1, + 'rarete' => 1, // commun + ], + [ + 'id' => 13, + 'nom' => 'George', + 'code' => '😜', + 'force' => 11.0, + 'robustesse' => 10.0, + 'intelligence' => 12.0, + 'vitesse' => 13.5, + 'nbCombatGagne' => 2, + 'rarete' => 3, // épique + ], + [ + 'id' => 14, + 'nom' => 'Hannah', + 'code' => '😏', + 'force' => 3.0, + 'robustesse' => 2.5, + 'intelligence' => 4.0, + 'vitesse' => 3.5, + 'nbCombatGagne' => 0, + 'rarete' => 2, // rare + ], + [ + 'id' => 15, + 'nom' => 'Ian', + 'code' => '😬', + 'force' => 17.0, + 'robustesse' => 14.0, + 'intelligence' => 16.0, + 'vitesse' => 18.0, + 'nbCombatGagne' => 7, + 'rarete' => 5, // légendaire + ], + ]; + + foreach ($emojisDeBase as &$emoji) { + $emoji['color'] = match ($emoji['rarete']) { + 2 => 'green', + 3 => 'purple', + 4 => 'red', + 5 => 'gold', + default => 'gray', + }; + } + foreach ($emojisCrees as &$emoji) { $emoji['color'] = match ($emoji['rarete']) { - 1 => 'green', // commun - 2 => 'purple', // épique - 3 => 'red', // mythique - 4 => 'gold', // légendaire - default => 'gray' + 2 => 'green', + 3 => 'purple', + 4 => 'red', + 5 => 'gold', + default => 'gray', }; } + return $this->render('home/index.html.twig', [ - 'emojis' => $emojis, + 'emojisDeBase' => $emojisDeBase, + 'emojisCrees' => $emojisCrees, ]); } } diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php new file mode 100644 index 0000000..1b26f59 --- /dev/null +++ b/src/Controller/LoginController.php @@ -0,0 +1,29 @@ +getUser()) { + return $this->redirectToRoute('app_home'); + } + $error = $authenticationUtils->getLastAuthenticationError(); + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('auth/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); + } + + #[Route(path: '/logout', name: 'app_logout')] + public function logout(): void + { + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + } +} diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000..50f4cf5 --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,48 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $user->setPassword( + $userPasswordHasher->hashPassword( + $user, + $form->get('plainPassword')->getData() + ) + ); + $user->setRoles(['ROLE_USER']); + + $entityManager->persist($user); + $entityManager->flush(); + + return $userAuthenticator->authenticateUser( + $user, + $authenticator, + $request + ); + } + + return $this->render('auth/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..b4006be --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,59 @@ +entityManager = $entityManager; + } + + #[Route('/{userId}', name: 'get_by_id', methods: ['GET'])] + public function getUserById(int $userId): JsonResponse + { + $user = $this->entityManager->getRepository(User::class)->find($userId); + + if (!$user) { + return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND); + } + + $data = [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'roles' => $user->getRoles(), + ]; + + return $this->json($data); + } + + #[Route('/{userId}/emojis', name: 'get_emojis_by_user', methods: ['GET'])] + public function getEmojisByUserId(int $userId): JsonResponse + { + $user = $this->entityManager->getRepository(User::class)->find($userId); + + if (!$user) { + return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND); + } + + $emojis = $user->getEmojis()->map(function ($emoji) { + return [ + 'id' => $emoji->getId(), + 'name' => $emoji->getName(), + 'code' => $emoji->getCode(), + ]; + })->toArray(); + + return $this->json($emojis); + } +} diff --git a/src/Entity/Emoji.php b/src/Entity/Emoji.php index f3bb04a..8668ff8 100644 --- a/src/Entity/Emoji.php +++ b/src/Entity/Emoji.php @@ -4,8 +4,10 @@ namespace App\Entity; use App\Repository\EmojiRepository; use Doctrine\ORM\Mapping as ORM; +use ApiPlatform\Metadata\ApiResource; #[ORM\Entity(repositoryClass: EmojiRepository::class)] +#[ApiResource] class Emoji { #[ORM\Id] @@ -160,4 +162,13 @@ class Emoji $this->rarity = $rarity; return $this; } + + public function wonFight() : self{ + $this->fightsWon = $this->fightsWon + 1; + $this->strength += 1; + $this->toughness += 1; + $this->intelligence += 1; + $this->speed += 1; + return $this; + } } diff --git a/src/Entity/Rarity.php b/src/Entity/Rarity.php index 0df259d..8ca232f 100644 --- a/src/Entity/Rarity.php +++ b/src/Entity/Rarity.php @@ -4,8 +4,10 @@ namespace App\Entity; use App\Repository\RarityRepository; use Doctrine\ORM\Mapping as ORM; +use ApiPlatform\Metadata\ApiResource; #[ORM\Entity(repositoryClass: RarityRepository::class)] +#[ApiResource] class Rarity { #[ORM\Id] diff --git a/src/Entity/StockEmoji.php b/src/Entity/StockEmoji.php new file mode 100644 index 0000000..91eab38 --- /dev/null +++ b/src/Entity/StockEmoji.php @@ -0,0 +1,52 @@ +id; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(string $code): self + { + $this->code = $code; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..566bfcc --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,118 @@ +emojis = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function setUsername(string $username): static + { + $this->username = $username; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string + { + return (string) $this->username; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + public function getEmojis(): Collection +{ + return $this->emojis; +} + + /** + * @see UserInterface + */ + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } +} diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 0000000..b5f3be1 --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,44 @@ +add('username') + ->add('plainPassword', PasswordType::class, [ + 'mapped' => false, + 'attr' => ['autocomplete' => 'new-password'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + 'max' => 4096, + ]), + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Repository/StockEmojiRepository.php b/src/Repository/StockEmojiRepository.php new file mode 100644 index 0000000..a50b840 --- /dev/null +++ b/src/Repository/StockEmojiRepository.php @@ -0,0 +1,48 @@ + + * + * @method StockEmoji|null find($id, $lockMode = null, $lockVersion = null) + * @method StockEmoji|null findOneBy(array $criteria, array $orderBy = null) + * @method StockEmoji[] findAll() + * @method StockEmoji[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class StockEmojiRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, StockEmoji::class); + } + +// /** +// * @return StockEmoji[] Returns an array of StockEmoji objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('s') +// ->andWhere('s.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('s.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?StockEmoji +// { +// return $this->createQueryBuilder('s') +// ->andWhere('s.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..c788f46 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,67 @@ + + * + * @implements PasswordUpgraderInterface + * + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php new file mode 100644 index 0000000..84bebf1 --- /dev/null +++ b/src/Security/LoginFormAuthenticator.php @@ -0,0 +1,59 @@ +request->get('username', ''); + + $request->getSession()->set(Security::LAST_USERNAME, $username); + + return new Passport( + new UserBadge($username), + new PasswordCredentials($request->request->get('password', '')), + [ + new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')), + new RememberMeBadge(), + ] + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { + return new RedirectResponse($targetPath); + } + + return new RedirectResponse($this->urlGenerator->generate('app_home')); + } + + protected function getLoginUrl(Request $request): string + { + return $this->urlGenerator->generate(self::LOGIN_ROUTE); + } + +} diff --git a/templates/auth/login.html.twig b/templates/auth/login.html.twig new file mode 100644 index 0000000..e6b945f --- /dev/null +++ b/templates/auth/login.html.twig @@ -0,0 +1,43 @@ +{% extends 'base.html.twig' %} + +{% block title %}Connexion{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +
+ +

🔐 Connexion

+ + {% if error %} + + {% endif %} + + {% if app.user %} +
+ Connecté en tant que {{ app.user.userIdentifier }}, + Se déconnecter +
+ {% endif %} + + + +
+
+

Pas encore de compte ?

+ Créer un compte +
+{% endblock %} diff --git a/templates/auth/register.html.twig b/templates/auth/register.html.twig new file mode 100644 index 0000000..3e7cb4d --- /dev/null +++ b/templates/auth/register.html.twig @@ -0,0 +1,30 @@ +{% extends 'base.html.twig' %} + +{% block title %}Inscription{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +
+

📝 Inscription

+ + {{ form_start(registrationForm, {'attr': {'class': 'register-form'}}) }} + {{ form_errors(registrationForm) }} + + {{ form_row(registrationForm.username) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Mot de passe' + }) }} + + + + + {{ form_end(registrationForm) }} +
+{% endblock %} diff --git a/templates/home/index.html.twig b/templates/home/index.html.twig index d48c919..056f8d5 100644 --- a/templates/home/index.html.twig +++ b/templates/home/index.html.twig @@ -7,9 +7,49 @@ {% endblock %} {% block body %} + {% if app.user %} +
+ Connecté en tant que {{ app.user.username }} +
+ +
+
+ {% endif %}

🧬 Ma collection de créatures 🐾

+

🌱 Vos créatures de base

+ +
+ {% for emoji in emojisDeBase %} +
+ +
Level {{ emoji.nbCombatGagne }}
+
{{ emoji.code }}
+
{{ emoji.nom }}
+
ℹ️
+ + +
+ {% endfor %} +
+ +
🔻
+ +

📦 Votre collection personnelle

+
@@ -32,18 +72,20 @@
-
Sélectionnez 2 créatures...
- -
- {% for emoji in emojis %} -
+
+ {% for emoji in emojisCrees %} +
Level {{ emoji.nbCombatGagne }}
{{ emoji.code }}
{{ emoji.nom }}
-
ℹ️
+
ℹ️
+ + +
Sélectionnez 2 créatures de votre choix pour commencer ...
+
- - + +
{% endblock %} {% block javascripts %} - + {% endblock %} \ No newline at end of file