creation de l'entite User #7

Merged
melvin-leveque merged 6 commits from feat/creation-entite into main 2026-03-17 12:06:42 +01:00
30 changed files with 8159 additions and 263 deletions
Showing only changes of commit 38b1293a27 - Show all commits

1
.env
View File

@@ -3,6 +3,7 @@ APP_SECRET=
APP_SHARE_DIR=var/share
APP_VERSION=0.0.1
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
MESSENGER_TRANSPORT_DSN="doctrine://default"
MAILER_DSN="smtp://localhost:1025"
DEFAULT_URI="http://localhost:8000"
KAZ_API_USER=

7
.gitignore vendored
View File

@@ -26,3 +26,10 @@ phpunit.xml
### OS / Misc ###
.DS_Store
Thumbs.db
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###

View File

@@ -2,9 +2,9 @@ import './stimulus_bootstrap.js';
/*
* Welcome to your app's main JavaScript file!
*
* This file will be included onto the page via the importmap() Twig function,
* which should already be in your base.html.twig.
* We recommend including the built version of this JavaScript file
* (and its CSS file) in your base layout (base.html.twig).
*/
import './styles/app.css';
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
// any CSS you import will output into a single css file (app.css in this case)
import './styles/app.css';

View File

@@ -0,0 +1,81 @@
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
// and thus this event-listener will not be executed.
document.addEventListener('submit', function (event) {
generateCsrfToken(event.target);
}, true);
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
Object.keys(h).map(function (k) {
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
});
});
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
removeCsrfToken(event.detail.formSubmission.formElement);
});
export function generateCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
let csrfToken = csrfField.value;
if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
}
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
if (csrfCookie && tokenCheck.test(csrfToken)) {
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
export function generateCsrfHeaders (formElement) {
const headers = {};
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return headers;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
headers[csrfCookie] = csrfField.value;
}
return headers;
}
export function removeCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';

View File

@@ -0,0 +1,16 @@
import { Controller } from '@hotwired/stimulus';
/*
* This is an example Stimulus controller!
*
* Any element with a data-controller="hello" attribute will cause
* this controller to be executed. The name "hello" comes from the filename:
* hello_controller.js -> "hello"
*
* Delete this file or adapt it for your use!
*/
export default class extends Controller {
connect() {
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
}
}

View File

@@ -1,5 +1,10 @@
import { startStimulusApp } from '@symfony/stimulus-bundle';
import { startStimulusApp } from '@symfony/stimulus-bridge';
startStimulusApp();
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.[jt]sx?$/
));
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);

View File

@@ -1,3 +1,3 @@
body {
background-color: skyblue;
}
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -13,7 +13,6 @@
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.3",
"symfony/asset": "8.0.*",
"symfony/asset-mapper": "8.0.*",
"symfony/console": "8.0.*",
"symfony/doctrine-messenger": "8.0.*",
"symfony/dotenv": "8.0.*",
@@ -37,9 +36,11 @@
"symfony/string": "8.0.*",
"symfony/translation": "8.0.*",
"symfony/twig-bundle": "8.0.*",
"symfony/uid": "8.0.*",
"symfony/ux-turbo": "^2.32",
"symfony/validator": "8.0.*",
"symfony/web-link": "8.0.*",
"symfony/webpack-encore-bundle": "^2.4",
"symfony/yaml": "8.0.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
@@ -78,8 +79,7 @@
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd",
"importmap:install": "symfony-cmd"
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"

411
composer.lock generated
View File

@@ -4,85 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b80e1957a1c5f59c7aa332a9535d541a",
"content-hash": "fbe32351f598e5760355fd0d2795b6a0",
"packages": [
{
"name": "composer/semver",
"version": "3.4.4",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.4"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"time": "2025-08-20T19:15:30+00:00"
},
{
"name": "doctrine/collections",
"version": "2.6.0",
@@ -1964,87 +1887,6 @@
],
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/asset-mapper",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset-mapper.git",
"reference": "14184221c21c2622e62f2c009a6cc25c5570e4ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/asset-mapper/zipball/14184221c21c2622e62f2c009a6cc25c5570e4ba",
"reference": "14184221c21c2622e62f2c009a6cc25c5570e4ba",
"shasum": ""
},
"require": {
"composer/semver": "^3.0",
"php": ">=8.4",
"symfony/filesystem": "^7.4|^8.0",
"symfony/http-client": "^7.4|^8.0"
},
"require-dev": {
"symfony/asset": "^7.4|^8.0",
"symfony/browser-kit": "^7.4|^8.0",
"symfony/console": "^7.4|^8.0",
"symfony/event-dispatcher-contracts": "^3.0",
"symfony/finder": "^7.4|^8.0",
"symfony/framework-bundle": "^7.4|^8.0",
"symfony/http-foundation": "^7.4|^8.0",
"symfony/http-kernel": "^7.4|^8.0",
"symfony/process": "^7.4|^8.0",
"symfony/runtime": "^7.4|^8.0",
"symfony/web-link": "^7.4|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\AssetMapper\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Maps directories of assets & makes them available in a public directory with versioned filenames.",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/asset-mapper/tree/v8.0.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/cache",
"version": "v8.0.5",
@@ -5213,6 +5055,89 @@
],
"time": "2025-06-23T16:12:55+00:00"
},
{
"name": "symfony/polyfill-uuid",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-uuid.git",
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-uuid": "*"
},
"suggest": {
"ext-uuid": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Uuid\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Grégoire Pineau",
"email": "lyrixx@lyrixx.info"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for uuid functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"uuid"
],
"support": {
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/process",
"version": "v8.0.5",
@@ -5610,16 +5535,16 @@
},
{
"name": "symfony/security-bundle",
"version": "v8.0.4",
"version": "v8.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
"reference": "c170650a00ba724be3455852747af600a2f042b4"
"reference": "73ba33c215a5e4516c7045c26f6fec71e4ab5727"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/security-bundle/zipball/c170650a00ba724be3455852747af600a2f042b4",
"reference": "c170650a00ba724be3455852747af600a2f042b4",
"url": "https://api.github.com/repos/symfony/security-bundle/zipball/73ba33c215a5e4516c7045c26f6fec71e4ab5727",
"reference": "73ba33c215a5e4516c7045c26f6fec71e4ab5727",
"shasum": ""
},
"require": {
@@ -5686,7 +5611,7 @@
"description": "Provides a tight integration of the Security component into the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/security-bundle/tree/v8.0.4"
"source": "https://github.com/symfony/security-bundle/tree/v8.0.6"
},
"funding": [
{
@@ -5706,7 +5631,7 @@
"type": "tidelift"
}
],
"time": "2026-01-10T13:58:55+00:00"
"time": "2026-02-22T22:01:53+00:00"
},
{
"name": "symfony/security-core",
@@ -6808,6 +6733,84 @@
],
"time": "2026-01-09T12:15:10+00:00"
},
{
"name": "symfony/uid",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
"reference": "8b81bd3700f5c1913c22a3266a647aa1bb974435"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/uid/zipball/8b81bd3700f5c1913c22a3266a647aa1bb974435",
"reference": "8b81bd3700f5c1913c22a3266a647aa1bb974435",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/polyfill-uuid": "^1.15"
},
"require-dev": {
"symfony/console": "^7.4|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Uid\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Grégoire Pineau",
"email": "lyrixx@lyrixx.info"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to generate and represent UIDs",
"homepage": "https://symfony.com",
"keywords": [
"UID",
"ulid",
"uuid"
],
"support": {
"source": "https://github.com/symfony/uid/tree/v8.0.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-01-03T23:40:55+00:00"
},
{
"name": "symfony/ux-turbo",
"version": "v2.32.0",
@@ -7256,6 +7259,82 @@
],
"time": "2026-01-01T23:07:29+00:00"
},
{
"name": "symfony/webpack-encore-bundle",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/webpack-encore-bundle.git",
"reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e",
"reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e",
"shasum": ""
},
"require": {
"php": ">=8.1.0",
"symfony/asset": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/config": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/http-kernel": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0"
},
"require-dev": {
"symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/http-client": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/web-link": "^5.4 || ^6.2 || ^7.0 || ^8.0"
},
"type": "symfony-bundle",
"extra": {
"thanks": {
"url": "https://github.com/symfony/webpack-encore",
"name": "symfony/webpack-encore"
}
},
"autoload": {
"psr-4": {
"Symfony\\WebpackEncoreBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Integration of your Symfony app with Webpack Encore",
"support": {
"issues": "https://github.com/symfony/webpack-encore-bundle/issues",
"source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-11-27T13:41:46+00:00"
},
{
"name": "symfony/yaml",
"version": "v8.0.1",
@@ -9824,5 +9903,5 @@
"ext-iconv": "*"
},
"platform-dev": [],
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}

View File

@@ -7,10 +7,11 @@ return [
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
];

View File

@@ -1,11 +0,0 @@
framework:
asset_mapper:
# The paths to make available to the asset mapper.
paths:
- assets/
missing_import_mode: strict
when@prod:
framework:
asset_mapper:
missing_import_mode: warn

View File

@@ -1,20 +1,40 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
# comment sont hachés nos mots de passe
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
# d'où viennent nos utilisateurs ?
providers:
users_in_memory: { memory: null }
# used to reload user from session & other features (e.g. switch_user)
app_user_provider: # un nom unique
entity: # nos utilisateurs sont en base de données
class: App\Entity\User # c'est cette entité-là
property: email # représentée par cette propriété-ci
# activation du système de sécurité
firewalls:
# permet de désactiver sur la debug bar, le profiler, etc...
dev:
# Ensure dev tools and static assets are always allowed
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
# notre pare-feu principal, activé partout par défaut
main:
lazy: true
provider: users_in_memory
provider: app_user_provider
# Le formulaire de connexion sera automatiquement protégé des attaques CSRF.
# La classe FormLoginAuthenticator est automatiquement utilisée en interne par Symfony,
# il n'y a plus de classe de type Authenticator à gérer soi-même.
form_login:
login_path: app_login
check_path: app_login
enable_csrf: true
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
# Activate different ways to authenticate:
# https://symfony.com/doc/current/security.html#the-firewall
@@ -23,6 +43,7 @@ security:
# switch_user: true
# Note: Only the *first* matching rule is applied
# autorisations
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }

View File

@@ -0,0 +1,45 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# If you have multiple builds:
# builds:
# frontend: '%kernel.project_dir%/public/frontend/build'
# pass the build name as the 3rd argument to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
#when@test:
# webpack_encore:
# strict_mode: false

View File

@@ -280,7 +280,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* }>,
* },
* asset_mapper?: bool|array{ // Asset Mapper configuration
* enabled?: bool|Param, // Default: true
* enabled?: bool|Param, // Default: false
* paths?: array<string, scalar|Param|null>,
* excluded_patterns?: list<scalar|Param|null>,
* exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true
@@ -638,7 +638,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* }>,
* },
* uid?: bool|array{ // Uid configuration
* enabled?: bool|Param, // Default: false
* enabled?: bool|Param, // Default: true
* default_uuid_version?: 7|6|4|1|Param, // Default: 7
* name_based_uuid_version?: 5|3|Param, // Default: 5
* name_based_uuid_namespace?: scalar|Param|null,
@@ -938,20 +938,6 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* intercept_redirects?: bool|Param, // Default: false
* excluded_ajax_paths?: scalar|Param|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
* }
* @psalm-type StimulusConfig = array{
* controller_paths?: list<scalar|Param|null>,
* controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json"
* }
* @psalm-type TurboConfig = array{
* broadcast?: bool|array{
* enabled?: bool|Param, // Default: true
* entity_template_prefixes?: list<scalar|Param|null>,
* doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration
* enabled?: bool|Param, // Default: true
* },
* },
* default_transport?: scalar|Param|null, // Default: "default"
* }
* @psalm-type TwigExtraConfig = array{
* cache?: bool|array{
* enabled?: bool|Param, // Default: false
@@ -1455,6 +1441,30 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* generate_final_classes?: bool|Param, // Default: true
* generate_final_entities?: bool|Param, // Default: false
* }
* @psalm-type WebpackEncoreConfig = array{
* output_path: scalar|Param|null, // The path where Encore is building the assets - i.e. Encore.setOutputPath()
* crossorigin?: false|"anonymous"|"use-credentials"|Param, // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false
* preload?: bool|Param, // preload all rendered script and link tags automatically via the http2 Link header. // Default: false
* cache?: bool|Param, // Enable caching of the entry point file(s) // Default: false
* strict_mode?: bool|Param, // Throw an exception if the entrypoints.json file is missing or an entry is missing from the data // Default: true
* builds?: array<string, scalar|Param|null>,
* script_attributes?: array<string, scalar|Param|null>,
* link_attributes?: array<string, scalar|Param|null>,
* }
* @psalm-type StimulusConfig = array{
* controller_paths?: list<scalar|Param|null>,
* controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json"
* }
* @psalm-type TurboConfig = array{
* broadcast?: bool|array{
* enabled?: bool|Param, // Default: true
* entity_template_prefixes?: list<scalar|Param|null>,
* doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration
* enabled?: bool|Param, // Default: true
* },
* },
* default_transport?: scalar|Param|null, // Default: "default"
* }
* @psalm-type ConfigType = array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@@ -1463,11 +1473,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* doctrine?: DoctrineConfig,
* doctrine_migrations?: DoctrineMigrationsConfig,
* twig?: TwigConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* webpack_encore?: WebpackEncoreConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* "when@dev"?: array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@@ -1478,12 +1489,13 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* debug?: DebugConfig,
* twig?: TwigConfig,
* web_profiler?: WebProfilerConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* maker?: MakerConfig,
* webpack_encore?: WebpackEncoreConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* },
* "when@prod"?: array{
* imports?: ImportsConfig,
@@ -1493,11 +1505,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* doctrine?: DoctrineConfig,
* doctrine_migrations?: DoctrineMigrationsConfig,
* twig?: TwigConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* webpack_encore?: WebpackEncoreConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* },
* "when@test"?: array{
* imports?: ImportsConfig,
@@ -1508,11 +1521,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* doctrine_migrations?: DoctrineMigrationsConfig,
* twig?: TwigConfig,
* web_profiler?: WebProfilerConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* webpack_encore?: WebpackEncoreConfig,
* stimulus?: StimulusConfig,
* turbo?: TurboConfig,
* },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig,

View File

@@ -1,28 +0,0 @@
<?php
/**
* Returns the importmap for this application.
*
* - "path" is a path inside the asset mapper system. Use the
* "debug:asset-map" command to see the full list of paths.
*
* - "entrypoint" (JavaScript only) set to true for any module that will
* be used as an "entrypoint" (and passed to the importmap() Twig function).
*
* The "importmap:require" command can be used to add new entries to this file.
*/
return [
'app' => [
'path' => './assets/app.js',
'entrypoint' => true,
],
'@hotwired/stimulus' => [
'version' => '3.2.2',
],
'@symfony/stimulus-bundle' => [
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
],
'@hotwired/turbo' => [
'version' => '7.3.0',
],
];

View File

@@ -0,0 +1,35 @@
<?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 Version20260313151403 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 "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, email_de_secours VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE "user"');
$this->addSql('DROP TABLE messenger_messages');
}
}

6456
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.0",
"@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.0 || ^8.0",
"@symfony/stimulus-bridge": "^3.2.0 || ^4.0.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@symfony/webpack-encore": "^6.0.0",
"core-js": "^3.38.0",
"regenerator-runtime": "^0.13.9",
"tailwindcss": "^3.4.19",
"webpack": "^5.74.0",
"webpack-cli": "^5.1.0"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
},
"dependencies": {
"@tailwindcss/postcss": "^4.2.1",
"postcss": "^8.5.8",
"postcss-loader": "^8.2.1"
}
}

5
postcss.config.mjs Normal file
View File

@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

631
public/css/app.css Normal file
View File

@@ -0,0 +1,631 @@
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
/*
! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden="until-found"])) {
display: none;
}
.visible {
visibility: visible;
}
.static {
position: static;
}
.fixed {
position: fixed;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.block {
display: block;
}
.hidden {
display: none;
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.rounded-lg {
border-radius: 0.5rem;
}
.border {
border-width: 1px;
}
.border-red-200 {
--tw-border-opacity: 1;
border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));
}
.bg-red-50 {
--tw-bg-opacity: 1;
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
}
.p-4 {
padding: 1rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-red-800 {
--tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity, 1));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

View File

@@ -8,7 +8,7 @@ use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/hello')]
#[Route('/hello', name: 'app_home', methods: ['GET'])]
public function hello(): Response
{
return $this->render('home/hello.html.twig', [

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Controller;
use LogicException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route(path: '/login', name: 'app_login', methods: ['GET','POST'])]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// si on a un utilisateur déjà connecté, alors on le redirige sur la page d'accueil
if ($this->getUser()) {
return $this->redirectToRoute('app_home');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
#[Route(path: '/logout', name: 'app_logout', methods: ['POST'])]
public function logout(): void
{
throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}

View File

@@ -14,6 +14,9 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class UserController extends AbstractController
{
// TODO : UserPasswordHasherInterface
melvin-leveque marked this conversation as resolved
Review

TODO : UserPasswordHasheInterface ?
Cela signifie ajouter une route pour changer de mot de passe ? Dans ce cas pourquoi ne pas la mettre dans SecurityController ?

Sinon pourquoi intégrer cette interface ?

TODO : UserPasswordHasheInterface ? Cela signifie ajouter une route pour changer de mot de passe ? Dans ce cas pourquoi ne pas la mettre dans SecurityController ? Sinon pourquoi intégrer cette interface ?
Review

je voulais que le SecurityController ne serve qu'à contrôler les entrées et sorties. Dans ma tête, le "UserPasswordHasheInterface" devait être dans le UserController, parce qu'il servira à l'adhérent dans son espace KAZ à modifier son mot de passe

je voulais que le SecurityController ne serve qu'à contrôler les entrées et sorties. Dans ma tête, le "UserPasswordHasheInterface" devait être dans le UserController, parce qu'il servira à l'adhérent dans son espace KAZ à modifier son mot de passe
// voir : https://symfony.com/doc/current/security/passwords.html#hashing-the-password
/**
* Permet de vérifier si un utilisateur existe dans le ldap.
*
@@ -27,7 +30,7 @@ class UserController extends AbstractController
* @throws ServerExceptionInterface
* @throws TransportExceptionInterface
*/
#[Route('/user/{email}')]
#[Route('/user/{email}', name: 'app_user', methods: ['GET'])]
public function index(string $email, KazApiService $apiClient): Response
{
$exist = $apiClient->getUserData($email);

268
src/Entity/User.php Normal file
View File

@@ -0,0 +1,268 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\Column(type: 'uuid', unique: true)]
private ?Uuid $id;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
/**
* @var list<string> The user roles
*/
#[ORM\Column]
private array $roles = [];
/**
* @var ?string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $emailQuota = null;
#[ORM\Column(length: 255)]
private ?string $emailDeSecours = null;
#[ORM\Column(length: 255)]
private ?string $identifiantKaz = null;
#[ORM\Column(length: 255)]
private ?string $quota = null;
#[ORM\Column]
private ?bool $hasNextcloudAccess = null;
#[ORM\Column(length: 255)]
private ?string $nextcloudQuota = null;
melvin-leveque marked this conversation as resolved
Review

Pourquoi 1GB et pas null comme les autres ?

Pourquoi 1GB et pas null comme les autres ?
Review

parce que je me suis trompée ^^ je devais le mettre sur emailQuota

parce que je me suis trompée ^^ je devais le mettre sur emailQuota
#[ORM\Column]
private ?bool $hasMobilizon = null;
#[ORM\Column]
private ?bool $hasAgoraAccess = null;
#[ORM\Column(length: 255)]
private ?string $lastname = null;
#[ORM\Column(length: 255)]
private ?string $firstname = null;
public function getId(): ?Uuid
{
return $this->id;
}
public function setId(Uuid $id): static
{
$this->id = $id;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
melvin-leveque marked this conversation as resolved
Review

On a pas besoin de caster l'email en String, le setter du mail prend une String donc on aura forcément une variable de ce type.
return $this->email; fonctionnera très bien

On a pas besoin de caster l'email en String, le setter du mail prend une String donc on aura forcément une variable de ce type. `return $this->email;` fonctionnera très bien
Review

je pense que ça s'est généré avec mon symfony console make:user

je pense que ça s'est généré avec mon symfony console make:user
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
melvin-leveque marked this conversation as resolved
Review

Il ne faut pas mettre la valeur par défaut dans un getter.
Avec le code actuel, si on créer un utilisateur avec le rôle ROLE_USER alors on récupérera ['ROLE_USER', 'ROLE_USER'] en appelant cette méthode.

Il ne faut pas mettre la valeur par défaut dans un getter. Avec le code actuel, si on créer un utilisateur avec le rôle ROLE_USER alors on récupérera ['ROLE_USER', 'ROLE_USER'] en appelant cette méthode.
Review

pareil que l'email, c'est le make:user qui a généré, je n'ai pas vérifié derrière (c'est d'ailleurs pour ça que les commentaires sont en anglais)

pareil que l'email, c'est le make:user qui a généré, je n'ai pas vérifié derrière (c'est d'ailleurs pour ça que les commentaires sont en anglais)
return array_unique($roles);
}
/**
* @param list<string> $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;
}
/**
* Ensure the session doesn't contain actual password hashes by CRC32C-hashing them, as supported since Symfony 7.3.
*/
public function __serialize(): array
{
$data = (array) $this;
$data["\0".self::class."\0password"] = hash('crc32c', $this->password);
return $data;
}
public function getEmailQuota(): ?string
{
return $this->emailQuota;
}
public function setEmailQuota(string $emailQuota): static
{
$this->emailQuota = $emailQuota;
return $this;
}
public function getEmailDeSecours(): ?string
{
return $this->emailDeSecours;
}
public function setEmailDeSecours(string $emailDeSecours): static
{
$this->emailDeSecours = $emailDeSecours;
return $this;
}
public function getIdentifiantKaz(): ?string
{
return $this->identifiantKaz;
}
public function setIdentifiantKaz(string $identifiantKaz): static
{
$this->identifiantKaz = $identifiantKaz;
return $this;
}
public function getQuota(): ?string
{
return $this->quota;
}
public function setQuota(string $quota): static
{
$this->quota = $quota;
return $this;
}
public function hasNextcloudAccess(): ?bool
{
return $this->hasNextcloudAccess;
}
public function setHasNextcloudAccess(bool $hasNextcloudAccess): static
{
$this->hasNextcloudAccess = $hasNextcloudAccess;
return $this;
}
public function getNextcloudQuota(): ?string
{
return $this->nextcloudQuota;
}
public function setNextcloudQuota(string $nextcloudQuota): static
{
$this->nextcloudQuota = $nextcloudQuota;
return $this;
}
public function hasMobilizon(): ?bool
{
return $this->hasMobilizon;
}
public function setHasMobilizon(bool $hasMobilizon): static
{
$this->hasMobilizon = $hasMobilizon;
return $this;
}
public function hasAgoraAccess(): ?bool
{
return $this->hasAgoraAccess;
}
public function setHasAgoraAccess(bool $hasAgoraAccess): static
{
$this->hasAgoraAccess = $hasAgoraAccess;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): static
{
$this->lastname = $lastname;
return $this;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): static
{
$this->firstname = $firstname;
return $this;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
melvin-leveque marked this conversation as resolved Outdated

!! Code commenté !!
Il faut enlever l'exemple et créer les bonnes méthodes associé si besoin !

!! Code commenté !! Il faut enlever l'exemple et créer les bonnes méthodes associé si besoin !

oui t'as raison !!! (>_<)

oui t'as raison !!! (>_<)
}
/**
* 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()
// ;
// }
}

View File

@@ -50,21 +50,6 @@
"bin/phpunit"
]
},
"symfony/asset-mapper": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
},
"files": [
"assets/app.js",
"assets/styles/app.css",
"config/packages/asset_mapper.yaml",
"importmap.php"
]
},
"symfony/console": {
"version": "8.0",
"recipe": {
@@ -270,6 +255,15 @@
"templates/base.html.twig"
]
},
"symfony/uid": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
}
},
"symfony/ux-turbo": {
"version": "2.32",
"recipe": {
@@ -319,6 +313,22 @@
"config/packages/messenger.yaml"
]
},
"symfony/webpack-encore-bundle": {
"version": "2.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.0",
"ref": "719f6110345acb6495e496601fc1b4977d7102b3"
},
"files": [
"assets/app.js",
"assets/styles/app.css",
"config/packages/webpack_encore.yaml",
"package.json",
"webpack.config.js"
]
},
"twig/extra-bundle": {
"version": "v3.23.0"
}

11
tailwind.config.js Normal file
View File

@@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./templates/**/*.html.twig",
"./assets/**/*.js",
],
theme: {
extend: {},
},
plugins: [],
}

View File

@@ -5,10 +5,10 @@
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
<link rel="stylesheet" href="/assets/styles/app.css">
{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
<body>

View File

@@ -0,0 +1,39 @@
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 border border-red-200">
Email ou mot de passe invalide.
</div>
{% endif %}
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ logout_path() }}">Se déconnecter</a>
</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Se connecter à mon compte KAZ</h1>
<label for="username">Email</label>
<input type="email" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="email" required autofocus>
<label for="password">Mot de passe</label>
<input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
{# TODO : vérifier qu'il y a bien une option "Se souvenir de moi" dans mon firewall pour activer la fonctionnalité. #}
{# pour en savoir plus : https://symfony.com/doc/current/security/remember_me.html #}
<div class="checkbox mb-3">
<input type="checkbox" name="_remember_me" id="_remember_me">
<label for="_remember_me">Se souvenir de moi</label>
</div>
<button class="btn btn-lg btn-primary" type="submit">
Se connecter
</button>
</form>
{% endblock %}

79
webpack.config.js Normal file
View File

@@ -0,0 +1,79 @@
const Encore = require('@symfony/webpack-encore');
// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
// only needed for CDN's or subdirectory deploy
//.setManifestKeyPrefix('build/')
/*
* ENTRY CONFIG
*
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
*/
.addEntry('app', './assets/app.js')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks()
// enables the Symfony UX Stimulus bridge (used in assets/stimulus_bootstrap.js)
.enableStimulusBridge('./assets/controllers.json')
// will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app
.enableSingleRuntimeChunk()
/*
* FEATURE CONFIG
*
* Enable & configure other features below. For a full
* list of features, see:
* https://symfony.com/doc/current/frontend.html#adding-more-features
*/
.cleanupOutputBeforeBuild()
// Displays build status system notifications to the user
// .enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())
// configure Babel
// .configureBabel((config) => {
// config.plugins.push('@babel/a-babel-plugin');
// })
// enables and configure @babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = '3.38';
})
// enables Sass/SCSS support
//.enableSassLoader()
// uncomment if you use TypeScript
//.enableTypeScriptLoader()
// uncomment if you use React
//.enableReactPreset()
// uncomment to get integrity="..." attributes on your script & link tags
// requires WebpackEncoreBundle 1.4 or higher
//.enableIntegrityHashes(Encore.isProduction())
// uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery()
;
module.exports = Encore.getWebpackConfig();