```
✨ feat(sw.js/app.js): Gère les notifications push et l'abonnement
Ajoute la gestion des notifications push avec abonnement via le
service worker et enregistre l'abonnement sur le serveur. Gère
l'affichage d'une bannière pour demander la permission.
```
This commit is contained in:
2
.env
2
.env
@@ -56,3 +56,5 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
|
|||||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||||
STRIPE_WEBHOOKS_SIGN=whsec_0DOZJAwgMwkcHl2RWXI8h8YItj9q7v3A
|
STRIPE_WEBHOOKS_SIGN=whsec_0DOZJAwgMwkcHl2RWXI8h8YItj9q7v3A
|
||||||
DEV_URL=https://3ea1cf1b1555.ngrok-free.app
|
DEV_URL=https://3ea1cf1b1555.ngrok-free.app
|
||||||
|
VAPID_PK=DsOg7jToRSD-VpNSV1Gt3YAhSwz4l-nqeu7yFvzbSxg
|
||||||
|
VAPID_PC=BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ www.e-cosplay.fr {
|
|||||||
|
|
||||||
header {
|
header {
|
||||||
-X-Robots-Tag
|
-X-Robots-Tag
|
||||||
Permissions-Policy "accelerometer=(), autoplay=(), camera=(), clipboard-write=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), publickey-credentials-get=(), usb=(), vr=(), screen-wake-lock=(), xr-spatial-tracking=(), bluetooth=(), ambient-light-sensor=(), battery=(), gamepad=(), notifications=(), push=()"
|
Permissions-Policy "accelerometer=(), autoplay=(), camera=(), clipboard-write=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), publickey-credentials-get=(), usb=(), screen-wake-lock=(), xr-spatial-tracking=(), bluetooth=(), gamepad=()"
|
||||||
Content-Security-Policy "base-uri 'self'; default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' https://datas.e-cosplay.fr https://*.cloudflareinsights.com https://storage.googleapis.com https://*.trustpilot.com; font-src 'self' https://fonts.gstatic.com;connect-src https://*.e-cosplay.fr https://*.cloudflareinsights.com https://fonts.googleapis.com https://widget.trustpilot.com/ https://challenges.cloudflare.com; frame-src 'self' https://*.trustpilot.com;"
|
Content-Security-Policy "base-uri 'self'; default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' https://datas.e-cosplay.fr https://*.cloudflareinsights.com https://storage.googleapis.com https://*.trustpilot.com; font-src 'self' https://fonts.gstatic.com;connect-src https://*.e-cosplay.fr https://*.cloudflareinsights.com https://fonts.googleapis.com https://widget.trustpilot.com/ https://challenges.cloudflare.com; frame-src 'self' https://*.trustpilot.com;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
221
assets/app.js
221
assets/app.js
@@ -2,6 +2,34 @@ import './app.scss'
|
|||||||
import * as Turbo from "@hotwired/turbo"
|
import * as Turbo from "@hotwired/turbo"
|
||||||
|
|
||||||
import {PaymentForm} from './PaymentForm'
|
import {PaymentForm} from './PaymentForm'
|
||||||
|
|
||||||
|
// --- CLÉ VAPID PUBLIQUE DU SERVEUR ---
|
||||||
|
// Cette clé est nécessaire pour identifier notre application auprès du service push.
|
||||||
|
const VAPID_PUBLIC_KEY = "BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit une chaîne Base64 URL Safe en Uint8Array.
|
||||||
|
* Nécessaire pour passer la clé VAPID publique à pushManager.subscribe().
|
||||||
|
* @param {string} base64String
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
function urlBase64ToUint8Array(base64String) {
|
||||||
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
|
const base64 = (base64String + padding)
|
||||||
|
.replace(/\-/g, '+')
|
||||||
|
.replace(/_/g, '/');
|
||||||
|
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction générique pour basculer la visibilité d'un menu déroulant.
|
* Fonction générique pour basculer la visibilité d'un menu déroulant.
|
||||||
* @param {HTMLElement} button - Le bouton qui déclenche l'action.
|
* @param {HTMLElement} button - Le bouton qui déclenche l'action.
|
||||||
@@ -76,19 +104,202 @@ function initializeUI() {
|
|||||||
|
|
||||||
// Simuler un panier non-vide au chargement (Mettre 0 pour un panier vide réel)
|
// Simuler un panier non-vide au chargement (Mettre 0 pour un panier vide réel)
|
||||||
updateCartDisplay(0);
|
updateCartDisplay(0);
|
||||||
|
|
||||||
|
// --- 4. Vérification de l'abonnement push au chargement (Logique demandée) ---
|
||||||
|
// Si la permission est déjà accordée, nous vérifions si l'abonnement est enregistré.
|
||||||
|
if ('Notification' in window && Notification.permission === 'granted') {
|
||||||
|
subscribeAndSave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tente d'abonner l'utilisateur aux notifications push via le Service Worker
|
||||||
|
* et envoie l'objet d'abonnement au backend (/notificationSub).
|
||||||
|
*/
|
||||||
|
async function subscribeAndSave() {
|
||||||
|
if (!('Notification' in window) || Notification.permission !== 'granted') {
|
||||||
|
console.log("Les notifications ne sont pas supportées ou la permission n'est pas accordée.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
||||||
|
console.error("Le Service Worker ou PushManager n'est pas disponible pour l'abonnement.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
let subscription = await registration.pushManager.getSubscription();
|
||||||
|
|
||||||
|
// 1. Si aucun abonnement n'existe, on essaie d'en créer un.
|
||||||
|
if (!subscription) {
|
||||||
|
console.log("Aucun abonnement existant trouvé. Tentative de nouvel abonnement...");
|
||||||
|
|
||||||
|
const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);
|
||||||
|
|
||||||
|
subscription = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: applicationServerKey
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
console.error("Échec de la création de l'abonnement. Le Service Worker est-il correctement enregistré et la clé VAPID valide ?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Abonnement push existant trouvé. Vérification/Mise à jour auprès du serveur.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Envoi (ou mise à jour) de l'abonnement au backend
|
||||||
|
const payload = { subscription: subscription.toJSON() };
|
||||||
|
|
||||||
|
const response = await fetch('/notificationSub', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("-> Abonnement aux notifications sauvegardé (ou vérifié) avec succès sur le serveur. <-");
|
||||||
|
} else {
|
||||||
|
console.error("-> Erreur lors de la sauvegarde de l'abonnement:", response.status, response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de l'obtention de l'abonnement ou de l'enregistrement:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tente de demander la permission si nécessaire et d'appeler l'abonnement.
|
||||||
|
* (Utilisée par le clic du bandeau)
|
||||||
|
*/
|
||||||
|
async function promptForPermissionAndSubscribe() {
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
console.error("Les Notifications ne sont pas supportées par ce navigateur.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demander la permission
|
||||||
|
try {
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
|
||||||
|
if (permission === 'granted') {
|
||||||
|
console.log("-> Permission de notification accordée. Lancement de l'abonnement. <-");
|
||||||
|
await subscribeAndSave();
|
||||||
|
} else {
|
||||||
|
console.log(`-> Permission de notification refusée ou ignorée (${permission}). <-`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la demande de permission:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche une petite carte de notification push temporaire en bas à gauche.
|
||||||
|
* N'affiche QUE si la permission n'est PAS accordée.
|
||||||
|
*/
|
||||||
|
function handleNotificationBanner() {
|
||||||
|
// Clé pour éviter de ré-afficher le bandeau si l'utilisateur vient de le fermer/cliquer.
|
||||||
|
const BANNER_ID = 'notification-prompt-banner';
|
||||||
|
const DURATION_MS = 15000; // 15 secondes d'affichage
|
||||||
|
|
||||||
|
// 1. NE PAS AFFICHER si la permission est déjà accordée (la vérification silencieuse est dans initializeUI)
|
||||||
|
if ('Notification' in window && Notification.permission === 'granted') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Si le bandeau existe déjà (e.g. navigation rapide), on quitte.
|
||||||
|
if (document.getElementById(BANNER_ID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- LA LOGIQUE DE CONTRÔLE DE TEMPS (LOCAL STORAGE) A ÉTÉ RETIRÉE ICI ---
|
||||||
|
|
||||||
|
// 3. Créer le conteneur du message
|
||||||
|
const banner = document.createElement('div');
|
||||||
|
banner.id = BANNER_ID;
|
||||||
|
banner.className = `fixed bottom-4 left-4 z-50 p-4 max-w-xs
|
||||||
|
bg-indigo-600 text-white rounded-xl shadow-2xl
|
||||||
|
transition-all duration-500 transform
|
||||||
|
opacity-0 translate-y-full
|
||||||
|
md:left-8 md:bottom-8`; // Style initial (masqué)
|
||||||
|
|
||||||
|
banner.innerHTML = `
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<p class="font-semibold text-sm leading-snug">
|
||||||
|
🔔 Activer les notifications
|
||||||
|
</p>
|
||||||
|
<button id="closeNotificationBanner"
|
||||||
|
aria-label="Fermer la notification"
|
||||||
|
class="ml-3 -mt-1 p-1 rounded-full text-indigo-200 hover:text-white hover:bg-indigo-700 transition">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-xs text-indigo-200">
|
||||||
|
Recevez les nouvelles, les promotions et les événements de l'association.
|
||||||
|
</p>
|
||||||
|
<button id="activateNotifications"
|
||||||
|
class="mt-3 w-full text-center py-2 bg-white text-indigo-600 font-bold text-sm rounded-lg
|
||||||
|
shadow hover:bg-gray-100 transition transform hover:scale-[1.02]">
|
||||||
|
Activer
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(banner);
|
||||||
|
|
||||||
|
// 4. Fonctions d'animation et de gestion
|
||||||
|
const hideBanner = () => {
|
||||||
|
// Déclenche l'animation de disparition
|
||||||
|
banner.classList.remove('opacity-100', 'translate-y-0');
|
||||||
|
banner.classList.add('opacity-0', 'translate-y-full');
|
||||||
|
// Supprime après l'animation pour nettoyer le DOM
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.body.contains(banner)) {
|
||||||
|
document.body.removeChild(banner);
|
||||||
|
}
|
||||||
|
}, 600);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clic sur le bouton de fermeture
|
||||||
|
document.getElementById('closeNotificationBanner').addEventListener('click', () => {
|
||||||
|
hideBanner();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clic sur le bouton d'activation -> Logique Push
|
||||||
|
document.getElementById('activateNotifications').addEventListener('click', async () => {
|
||||||
|
await promptForPermissionAndSubscribe();
|
||||||
|
// Fermer le bandeau après l'interaction (que ce soit accordé ou refusé)
|
||||||
|
hideBanner();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Affichage et Timer
|
||||||
|
|
||||||
|
// Montre le bandeau (déclencher l'animation)
|
||||||
|
setTimeout(() => {
|
||||||
|
banner.classList.remove('opacity-0', 'translate-y-full');
|
||||||
|
banner.classList.add('opacity-100', 'translate-y-0');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Cache le bandeau automatiquement après la durée définie
|
||||||
|
setTimeout(hideBanner, DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT ---
|
// --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT ---
|
||||||
document.addEventListener('DOMContentLoaded', ()=>{
|
document.addEventListener('DOMContentLoaded', ()=>{
|
||||||
customElements.define('payment-don',PaymentForm,{extends:'form'})
|
customElements.define('payment-don',PaymentForm,{extends:'form'})
|
||||||
|
|
||||||
|
// initializeUI appelle subscribeAndSave si la permission est accordée
|
||||||
initializeUI()
|
initializeUI()
|
||||||
const env = document.querySelector('meta[name="env"]')
|
if (typeof navigator.serviceWorker !== 'undefined') {
|
||||||
if(env.getAttribute('content') == "prod") {
|
// Assurez-vous que le Service Worker est bien enregistré en mode prod
|
||||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
navigator.serviceWorker.register('sw.js')
|
||||||
navigator.serviceWorker.register('sw.js')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// handleNotificationBanner n'affiche que si la permission n'est PAS accordée
|
||||||
|
handleNotificationBanner()
|
||||||
|
|
||||||
});
|
});
|
||||||
document.addEventListener('turbo:load', initializeUI);
|
document.addEventListener('turbo:load', initializeUI);
|
||||||
|
|
||||||
|
|||||||
33
migrations/Version20251119124756.php
Normal file
33
migrations/Version20251119124756.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20251119124756 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE sub (id SERIAL NOT NULL, subcriber TEXT NOT NULL, sub_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('COMMENT ON COLUMN sub.subcriber IS \'(DC2Type:array)\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE SCHEMA public');
|
||||||
|
$this->addSql('DROP TABLE sub');
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/assets/notif.png
Normal file
BIN
public/assets/notif.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
62
public/sw.js
62
public/sw.js
@@ -31,24 +31,54 @@ workbox.routing.registerRoute(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
// --- GESTION DES NOTIFICATIONS PUSH (Réception) ---
|
||||||
if (event.request.mode === 'navigate') {
|
self.addEventListener('push', (event) => {
|
||||||
event.respondWith((async () => {
|
if (event.data) {
|
||||||
try {
|
// Assurez-vous que le payload JSON envoyé par votre serveur contient
|
||||||
const preloadResp = await event.preloadResponse;
|
// 'title', 'message' et 'link'.
|
||||||
|
const data = event.data.json();
|
||||||
|
|
||||||
if (preloadResp) {
|
const title = data.title || 'Nouvelle Notification';
|
||||||
return preloadResp;
|
const message = data.message || 'Contenu mis à jour.';
|
||||||
}
|
const link = data.link || '/'; // Lien par défaut vers la racine
|
||||||
|
|
||||||
const networkResp = await fetch(event.request);
|
const options = {
|
||||||
return networkResp;
|
body: message,
|
||||||
} catch (error) {
|
// PATH MIS À JOUR ICI
|
||||||
|
icon: data.icon || '/assets/notif.png',
|
||||||
const cache = await caches.open(CACHE);
|
data: {
|
||||||
const cachedResp = await cache.match(offlineFallbackPage);
|
link: link // On stocke le lien pour le réutiliser au clic
|
||||||
return cachedResp;
|
|
||||||
}
|
}
|
||||||
})());
|
};
|
||||||
|
|
||||||
|
// Affiche la notification
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(title, options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- GESTION DES CLICS SUR LA NOTIFICATION ---
|
||||||
|
self.addEventListener('notificationclick', (event) => {
|
||||||
|
// Récupère le lien stocké dans la notification
|
||||||
|
const urlToOpen = event.notification.data.link || '/';
|
||||||
|
|
||||||
|
// Ferme la notification après le clic
|
||||||
|
event.notification.close();
|
||||||
|
|
||||||
|
// Ouvre l'URL associée, soit dans un onglet existant, soit dans un nouvel onglet
|
||||||
|
event.waitUntil(
|
||||||
|
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
|
||||||
|
|
||||||
|
// Tente de trouver un client existant pour naviguer
|
||||||
|
for (const client of clientList) {
|
||||||
|
if (client.url.endsWith(urlToOpen) && 'focus' in client) {
|
||||||
|
return client.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, ouvre une nouvelle fenêtre/onglet
|
||||||
|
return clients.openWindow(urlToOpen);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Entity\Account;
|
use App\Entity\Account;
|
||||||
use App\Entity\AccountResetPasswordRequest;
|
use App\Entity\AccountResetPasswordRequest;
|
||||||
|
use App\Entity\Sub;
|
||||||
use App\Form\RequestPasswordConfirmType;
|
use App\Form\RequestPasswordConfirmType;
|
||||||
use App\Form\RequestPasswordRequestType;
|
use App\Form\RequestPasswordRequestType;
|
||||||
|
use App\Repository\SubRepository;
|
||||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@@ -23,6 +25,22 @@ use Twig\Environment;
|
|||||||
class SecurityController extends AbstractController
|
class SecurityController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#[Route(path: '/notificationSub', name: 'app_notificationSub', options: ['sitemap' => false], methods: ['POST'])]
|
||||||
|
public function notificationSub(Request $request,SubRepository $subRepository,EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$content = json_decode($request->getContent(),true);
|
||||||
|
|
||||||
|
$sub = $subRepository->findOneBy(['subId'=>$content['subscription']['endpoint']]);
|
||||||
|
if(!$sub instanceof Sub){
|
||||||
|
$sub = new Sub();
|
||||||
|
$sub->setSubId($content['subscription']['endpoint']);
|
||||||
|
$sub->setSubcriber($content);
|
||||||
|
$entityManager->persist($sub);
|
||||||
|
}
|
||||||
|
$entityManager->flush();
|
||||||
|
return $this->json([]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route(path: '/connexion', name: 'app_login', options: ['sitemap' => false], methods: ['GET','POST'])]
|
#[Route(path: '/connexion', name: 'app_login', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||||
{
|
{
|
||||||
|
|||||||
51
src/Entity/Sub.php
Normal file
51
src/Entity/Sub.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\SubRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: SubRepository::class)]
|
||||||
|
class Sub
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::ARRAY)]
|
||||||
|
private array $subcriber = [];
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $subId = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubcriber(): array
|
||||||
|
{
|
||||||
|
return $this->subcriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubcriber(array $subcriber): static
|
||||||
|
{
|
||||||
|
$this->subcriber = $subcriber;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubId(): ?string
|
||||||
|
{
|
||||||
|
return $this->subId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubId(?string $subId): static
|
||||||
|
{
|
||||||
|
$this->subId = $subId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/Repository/SubRepository.php
Normal file
43
src/Repository/SubRepository.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Sub;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<Sub>
|
||||||
|
*/
|
||||||
|
class SubRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Sub::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Sub[] Returns an array of Sub 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): ?Sub
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('s')
|
||||||
|
// ->andWhere('s.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user