Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c22f9dbf2b | ||
|
|
27a1b09d62 | ||
|
|
7bbb693924 | ||
|
|
9661fd5d91 | ||
|
|
d9ab583879 | ||
|
|
5d41bda997 | ||
|
|
3d037083c6 | ||
|
|
a3e440c254 |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,15 +1,28 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Liste des évolutions du projet inventory
|
## [1.7.0] - 2026-03-02
|
||||||
|
|
||||||
## [0.0.0]
|
### Ajouts
|
||||||
### Parameters
|
- **Systeme de commentaires / tickets** : les utilisateurs peuvent laisser des commentaires sur les fiches (machines, pieces, composants, produits, categories, squelettes). Les gestionnaires peuvent les resoudre.
|
||||||
Ajouter dans le fichier .env
|
- **Page commentaires** (`/comments`) : vue centralisee avec filtres (statut, type d'entite), pagination et liens cliquables vers les fiches.
|
||||||
- DEFAULT_URI
|
- **Badge notifications** : compteur de commentaires ouverts sur l'avatar utilisateur et dans le menu profil (polling 60s).
|
||||||
- DATABASE_URL
|
- **Controle d'acces par roles** : ROLE_ADMIN, ROLE_GESTIONNAIRE, ROLE_VIEWER avec permissions granulaires sur toutes les pages.
|
||||||
|
- **Badge de role** dans le dropdown du profil utilisateur.
|
||||||
|
- **Journal d'audit etendu** : audit logging sur machines, constructeurs, types de modeles, documents et conversions.
|
||||||
|
- **Commande `app:init-profile-passwords`** : initialisation en masse des mots de passe et roles.
|
||||||
|
|
||||||
### Added
|
### Corrections
|
||||||
|
- Toggle switch pour les champs personnalises booleens (remplace les checkboxes).
|
||||||
|
- Recherche constructeur : filtrage cote client au lieu d'appels API debounce.
|
||||||
|
- Prevention des doublons de noms de constructeurs et de references de pieces (contraintes unique).
|
||||||
|
- Fix creation de squelettes machines : pagination, duplication, champs personnalises.
|
||||||
|
|
||||||
### Changed
|
### Migration requise
|
||||||
|
```bash
|
||||||
|
docker compose exec php php bin/console doctrine:migrations:migrate
|
||||||
|
docker compose exec php php bin/console app:init-profile-passwords
|
||||||
|
```
|
||||||
|
|
||||||
### Fixed
|
## [1.6.0] - 2026-02-xx
|
||||||
|
|
||||||
|
- Version initiale avec gestion du parc machines, pieces, composants, produits et categories.
|
||||||
|
|||||||
Submodule Inventory_frontend updated: 6bed715b7f...a98ab8c275
@@ -29,33 +29,36 @@ security:
|
|||||||
success_handler: lexik_jwt_authentication.handler.authentication_success
|
success_handler: lexik_jwt_authentication.handler.authentication_success
|
||||||
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
||||||
|
|
||||||
session_profile:
|
session_public:
|
||||||
pattern: ^/api/session
|
pattern: ^/api/session/profiles?$
|
||||||
stateless: false
|
security: false
|
||||||
|
|
||||||
session_api:
|
|
||||||
pattern: ^/api/(sites|machines|documents|profiles)
|
|
||||||
stateless: false
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
pattern: ^/api
|
pattern: ^/api
|
||||||
stateless: false
|
stateless: true
|
||||||
|
custom_authenticators:
|
||||||
|
- App\Security\SessionProfileAuthenticator
|
||||||
|
|
||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
|
|
||||||
|
role_hierarchy:
|
||||||
|
ROLE_ADMIN: ROLE_GESTIONNAIRE
|
||||||
|
ROLE_GESTIONNAIRE: ROLE_VIEWER
|
||||||
|
ROLE_VIEWER: ROLE_USER
|
||||||
|
|
||||||
# Note: Only the *first* matching rule is applied
|
# Note: Only the *first* matching rule is applied
|
||||||
access_control:
|
access_control:
|
||||||
- { path: ^/api/session/profile, roles: PUBLIC_ACCESS }
|
- { path: ^/api/session/profile$, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/session/profiles, roles: PUBLIC_ACCESS }
|
- { path: ^/api/session/profiles, roles: PUBLIC_ACCESS, methods: [GET] }
|
||||||
- { path: ^/api, roles: PUBLIC_ACCESS }
|
- { path: ^/api/admin, roles: ROLE_ADMIN }
|
||||||
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/test, roles: PUBLIC_ACCESS }
|
- { path: ^/api/test, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/docs, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/contexts, roles: PUBLIC_ACCESS }
|
- { path: ^/contexts, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/\.well-known, roles: PUBLIC_ACCESS }
|
- { path: ^/\.well-known, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/api, roles: ROLE_VIEWER }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
security:
|
security:
|
||||||
|
|||||||
51
migrations/Version20260302103003.php
Normal file
51
migrations/Version20260302103003.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260302103003 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create comments table + make piece reference unique instead of name';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// Comments table (IF NOT EXISTS in case first attempt partially succeeded)
|
||||||
|
$this->addSql('CREATE TABLE IF NOT EXISTS comments (id VARCHAR(36) NOT NULL, content TEXT NOT NULL, entity_type VARCHAR(50) NOT NULL, entity_id VARCHAR(36) NOT NULL, entity_name VARCHAR(255) DEFAULT NULL, author_id VARCHAR(36) NOT NULL, author_name VARCHAR(255) NOT NULL, status VARCHAR(20) NOT NULL, resolved_by_id VARCHAR(36) DEFAULT NULL, resolved_by_name VARCHAR(255) DEFAULT NULL, resolved_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))');
|
||||||
|
$this->addSql('CREATE INDEX IF NOT EXISTS idx_comment_entity_status ON comments (entity_type, entity_id, status)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN comments.resolved_at IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN comments.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN comments.updated_at IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
|
||||||
|
// Piece: remove unique on name
|
||||||
|
$this->addSql('DROP INDEX IF EXISTS uniq_b92d74725e237e06');
|
||||||
|
|
||||||
|
// Deduplicate piece references before adding unique constraint
|
||||||
|
$this->addSql("
|
||||||
|
UPDATE pieces p
|
||||||
|
SET reference = p.reference || '-' || LEFT(p.id, 6)
|
||||||
|
FROM (
|
||||||
|
SELECT id, reference,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY reference ORDER BY createdat) AS rn
|
||||||
|
FROM pieces
|
||||||
|
WHERE reference IS NOT NULL AND reference != ''
|
||||||
|
) dup
|
||||||
|
WHERE p.id = dup.id AND dup.rn > 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_pieces_reference ON pieces (reference)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS comments');
|
||||||
|
$this->addSql('DROP INDEX IF EXISTS uniq_pieces_reference');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uniq_b92d74725e237e06 ON pieces (name)');
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/Command/InitProfilePasswordsCommand.php
Normal file
85
src/Command/InitProfilePasswordsCommand.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:init-profile-passwords',
|
||||||
|
description: 'Initialize all profile passwords to first letter of firstName + "123"',
|
||||||
|
)]
|
||||||
|
class InitProfilePasswordsCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$all = $this->profiles->findAll();
|
||||||
|
|
||||||
|
if (0 === count($all)) {
|
||||||
|
$io->warning('Aucun profil trouvé.');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promote first profile to ROLE_ADMIN if none exists
|
||||||
|
$hasAdmin = false;
|
||||||
|
foreach ($all as $profile) {
|
||||||
|
if (in_array('ROLE_ADMIN', $profile->getRoles(), true)) {
|
||||||
|
$hasAdmin = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$isFirst = true;
|
||||||
|
$count = 0;
|
||||||
|
foreach ($all as $profile) {
|
||||||
|
// Set password: first letter of firstName + "123"
|
||||||
|
$firstLetter = mb_strtoupper(mb_substr($profile->getFirstName(), 0, 1));
|
||||||
|
$plain = $firstLetter.'123';
|
||||||
|
$hashed = $this->passwordHasher->hashPassword($profile, $plain);
|
||||||
|
$profile->setPassword($hashed);
|
||||||
|
|
||||||
|
// Set roles: first profile → ADMIN, others → VIEWER (minimum to use the app)
|
||||||
|
if (!$hasAdmin && $isFirst) {
|
||||||
|
$profile->setRoles(['ROLE_ADMIN']);
|
||||||
|
$io->writeln(sprintf(' %s %s → mdp: %s — ROLE_ADMIN', $profile->getFirstName(), $profile->getLastName(), $plain));
|
||||||
|
$isFirst = false;
|
||||||
|
} elseif (in_array('ROLE_USER', $profile->getRoles(), true) && !in_array('ROLE_VIEWER', $profile->getRoles(), true) && !in_array('ROLE_GESTIONNAIRE', $profile->getRoles(), true) && !in_array('ROLE_ADMIN', $profile->getRoles(), true)) {
|
||||||
|
$profile->setRoles(['ROLE_VIEWER']);
|
||||||
|
$io->writeln(sprintf(' %s %s → mdp: %s — ROLE_VIEWER', $profile->getFirstName(), $profile->getLastName(), $plain));
|
||||||
|
} else {
|
||||||
|
$io->writeln(sprintf(' %s %s → mdp: %s — %s', $profile->getFirstName(), $profile->getLastName(), $plain, implode(', ', $profile->getRoles())));
|
||||||
|
}
|
||||||
|
|
||||||
|
++$count;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$io->success(sprintf('%d mot(s) de passe initialisé(s).', $count));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,12 @@ namespace App\Controller;
|
|||||||
use App\Repository\AuditLogRepository;
|
use App\Repository\AuditLogRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class ActivityLogController
|
final class ActivityLogController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AuditLogRepository $auditLogs,
|
private readonly AuditLogRepository $auditLogs,
|
||||||
@@ -21,6 +22,8 @@ final class ActivityLogController
|
|||||||
#[Route('/api/activity-logs', name: 'api_activity_logs', methods: ['GET'])]
|
#[Route('/api/activity-logs', name: 'api_activity_logs', methods: ['GET'])]
|
||||||
public function __invoke(Request $request): JsonResponse
|
public function __invoke(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$page = max(1, $request->query->getInt('page', 1));
|
$page = max(1, $request->query->getInt('page', 1));
|
||||||
$itemsPerPage = min(100, max(1, $request->query->getInt('itemsPerPage', 30)));
|
$itemsPerPage = min(100, max(1, $request->query->getInt('itemsPerPage', 30)));
|
||||||
|
|
||||||
|
|||||||
193
src/Controller/AdminProfileController.php
Normal file
193
src/Controller/AdminProfileController.php
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
#[Route('/api/admin/profiles')]
|
||||||
|
final class AdminProfileController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('', name: 'admin_profiles_list', methods: ['GET'])]
|
||||||
|
public function list(): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$items = $this->profiles->findBy([], ['firstName' => 'ASC']);
|
||||||
|
|
||||||
|
return new JsonResponse(array_map([$this, 'serializeProfile'], $items));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('', name: 'admin_profiles_create', methods: ['POST'])]
|
||||||
|
public function create(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$payload = $request->toArray();
|
||||||
|
$firstName = trim((string) ($payload['firstName'] ?? ''));
|
||||||
|
$lastName = trim((string) ($payload['lastName'] ?? ''));
|
||||||
|
|
||||||
|
if ('' === $firstName || '' === $lastName) {
|
||||||
|
return new JsonResponse(['message' => 'firstName et lastName sont requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = trim((string) ($payload['email'] ?? ''));
|
||||||
|
$password = $payload['password'] ?? null;
|
||||||
|
$role = $payload['role'] ?? 'ROLE_VIEWER';
|
||||||
|
|
||||||
|
$allowedRoles = ['ROLE_ADMIN', 'ROLE_GESTIONNAIRE', 'ROLE_VIEWER', 'ROLE_USER'];
|
||||||
|
if (!in_array($role, $allowedRoles, true)) {
|
||||||
|
return new JsonResponse(['message' => 'Role invalide.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = new Profile();
|
||||||
|
$profile->setFirstName($firstName);
|
||||||
|
$profile->setLastName($lastName);
|
||||||
|
$profile->setIsActive(true);
|
||||||
|
$profile->setRoles([$role]);
|
||||||
|
|
||||||
|
if ('' !== $email) {
|
||||||
|
$profile->setEmail($email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $password && '' !== $password) {
|
||||||
|
$profile->setPassword(
|
||||||
|
$this->passwordHasher->hashPassword($profile, $password)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->persist($profile);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse($this->serializeProfile($profile), JsonResponse::HTTP_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/role', name: 'admin_profiles_update_role', methods: ['PUT'])]
|
||||||
|
public function updateRole(string $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$profile = $this->profiles->find($id);
|
||||||
|
if (!$profile) {
|
||||||
|
return new JsonResponse(['message' => 'Profil introuvable.'], JsonResponse::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = $request->toArray();
|
||||||
|
$role = $payload['role'] ?? null;
|
||||||
|
|
||||||
|
$allowedRoles = ['ROLE_ADMIN', 'ROLE_GESTIONNAIRE', 'ROLE_VIEWER', 'ROLE_USER'];
|
||||||
|
if (!$role || !in_array($role, $allowedRoles, true)) {
|
||||||
|
return new JsonResponse(['message' => 'Role invalide.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent removing the last admin
|
||||||
|
if (in_array('ROLE_ADMIN', $profile->getRoles(), true) && 'ROLE_ADMIN' !== $role) {
|
||||||
|
$adminCount = $this->countAdmins();
|
||||||
|
if ($adminCount <= 1) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Impossible de retirer le dernier administrateur.'],
|
||||||
|
JsonResponse::HTTP_CONFLICT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile->setRoles([$role]);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse($this->serializeProfile($profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/password', name: 'admin_profiles_update_password', methods: ['PUT'])]
|
||||||
|
public function updatePassword(string $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$profile = $this->profiles->find($id);
|
||||||
|
if (!$profile) {
|
||||||
|
return new JsonResponse(['message' => 'Profil introuvable.'], JsonResponse::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = $request->toArray();
|
||||||
|
$password = $payload['password'] ?? '';
|
||||||
|
|
||||||
|
if ('' === $password) {
|
||||||
|
return new JsonResponse(['message' => 'Le mot de passe est requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile->setPassword(
|
||||||
|
$this->passwordHasher->hashPassword($profile, $password)
|
||||||
|
);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse($this->serializeProfile($profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/deactivate', name: 'admin_profiles_deactivate', methods: ['PUT'])]
|
||||||
|
public function deactivate(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$profile = $this->profiles->find($id);
|
||||||
|
if (!$profile) {
|
||||||
|
return new JsonResponse(['message' => 'Profil introuvable.'], JsonResponse::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent deactivating the last admin
|
||||||
|
if (in_array('ROLE_ADMIN', $profile->getRoles(), true)) {
|
||||||
|
$adminCount = $this->countAdmins();
|
||||||
|
if ($adminCount <= 1) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Impossible de desactiver le dernier administrateur.'],
|
||||||
|
JsonResponse::HTTP_CONFLICT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile->setIsActive(false);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse($this->serializeProfile($profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serializeProfile(Profile $profile): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $profile->getId(),
|
||||||
|
'firstName' => $profile->getFirstName(),
|
||||||
|
'lastName' => $profile->getLastName(),
|
||||||
|
'email' => $profile->getEmail(),
|
||||||
|
'isActive' => $profile->isActive(),
|
||||||
|
'hasPassword' => null !== $profile->getPassword() && '' !== $profile->getPassword(),
|
||||||
|
'roles' => $profile->getRoles(),
|
||||||
|
'createdAt' => $profile->getCreatedAt()->format('c'),
|
||||||
|
'updatedAt' => $profile->getUpdatedAt()->format('c'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function countAdmins(): int
|
||||||
|
{
|
||||||
|
$all = $this->profiles->findBy(['isActive' => true]);
|
||||||
|
|
||||||
|
return count(array_filter(
|
||||||
|
$all,
|
||||||
|
static fn (Profile $p) => in_array('ROLE_ADMIN', $p->getRoles(), true)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/Controller/CommentController.php
Normal file
145
src/Controller/CommentController.php
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Comment;
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
#[Route('/api/comments')]
|
||||||
|
final class CommentController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('', name: 'api_comments_create', methods: ['POST'])]
|
||||||
|
public function create(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
|
$session = $request->getSession();
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
if (!$profileId) {
|
||||||
|
return $this->json(['message' => 'Aucun profil actif.'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = $this->profiles->find($profileId);
|
||||||
|
if (!$profile) {
|
||||||
|
return $this->json(['message' => 'Profil introuvable.'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_decode($request->getContent(), true);
|
||||||
|
if (!is_array($payload)) {
|
||||||
|
return $this->json(['message' => 'Payload JSON invalide.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = trim((string) ($payload['content'] ?? ''));
|
||||||
|
$entityType = trim((string) ($payload['entityType'] ?? ''));
|
||||||
|
$entityId = trim((string) ($payload['entityId'] ?? ''));
|
||||||
|
$entityName = isset($payload['entityName']) ? trim((string) $payload['entityName']) : null;
|
||||||
|
|
||||||
|
if ('' === $content) {
|
||||||
|
return $this->json(['message' => 'Le contenu est requis.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedTypes = ['machine', 'piece', 'composant', 'product', 'piece_category', 'component_category', 'product_category', 'machine_skeleton'];
|
||||||
|
if (!in_array($entityType, $allowedTypes, true)) {
|
||||||
|
return $this->json(['message' => 'Type d\'entité invalide.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $entityId) {
|
||||||
|
return $this->json(['message' => 'L\'identifiant de l\'entité est requis.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorName = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
|
if ('' === $authorName) {
|
||||||
|
$authorName = $profile->getEmail() ?? 'Inconnu';
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment = new Comment();
|
||||||
|
$comment->setContent($content);
|
||||||
|
$comment->setEntityType($entityType);
|
||||||
|
$comment->setEntityId($entityId);
|
||||||
|
$comment->setEntityName($entityName);
|
||||||
|
$comment->setAuthorId($profileId);
|
||||||
|
$comment->setAuthorName($authorName);
|
||||||
|
|
||||||
|
$this->entityManager->persist($comment);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json($this->normalize($comment), 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/resolve', name: 'api_comments_resolve', methods: ['PATCH'])]
|
||||||
|
public function resolve(string $id, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
|
$comment = $this->entityManager->getRepository(Comment::class)->find($id);
|
||||||
|
if (!$comment) {
|
||||||
|
return $this->json(['message' => 'Commentaire introuvable.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$session = $request->getSession();
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
$profile = $profileId ? $this->profiles->find($profileId) : null;
|
||||||
|
|
||||||
|
$resolverName = 'Inconnu';
|
||||||
|
if ($profile) {
|
||||||
|
$resolverName = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
|
if ('' === $resolverName) {
|
||||||
|
$resolverName = $profile->getEmail() ?? 'Inconnu';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment->setStatus('resolved');
|
||||||
|
$comment->setResolvedById($profileId);
|
||||||
|
$comment->setResolvedByName($resolverName);
|
||||||
|
$comment->setResolvedAt(new DateTimeImmutable());
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json($this->normalize($comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/stats/unresolved-count', name: 'api_comments_unresolved_count', methods: ['GET'])]
|
||||||
|
public function unresolvedCount(): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
|
$count = $this->entityManager->getRepository(Comment::class)
|
||||||
|
->count(['status' => 'open'])
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this->json(['count' => $count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalize(Comment $comment): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $comment->getId(),
|
||||||
|
'content' => $comment->getContent(),
|
||||||
|
'entityType' => $comment->getEntityType(),
|
||||||
|
'entityId' => $comment->getEntityId(),
|
||||||
|
'entityName' => $comment->getEntityName(),
|
||||||
|
'authorId' => $comment->getAuthorId(),
|
||||||
|
'authorName' => $comment->getAuthorName(),
|
||||||
|
'status' => $comment->getStatus(),
|
||||||
|
'resolvedById' => $comment->getResolvedById(),
|
||||||
|
'resolvedByName' => $comment->getResolvedByName(),
|
||||||
|
'resolvedAt' => $comment->getResolvedAt()?->format(DateTimeInterface::ATOM),
|
||||||
|
'createdAt' => $comment->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
|
'updatedAt' => $comment->getUpdatedAt()->format(DateTimeInterface::ATOM),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,12 @@ use App\Repository\AuditLogRepository;
|
|||||||
use App\Repository\ComposantRepository;
|
use App\Repository\ComposantRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class ComposantHistoryController
|
final class ComposantHistoryController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ComposantRepository $components,
|
private readonly ComposantRepository $components,
|
||||||
@@ -23,6 +24,8 @@ final class ComposantHistoryController
|
|||||||
#[Route('/api/composants/{id}/history', name: 'api_composant_history', methods: ['GET'])]
|
#[Route('/api/composants/{id}/history', name: 'api_composant_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$component = $this->components->find($id);
|
$component = $this->components->find($id);
|
||||||
if (!$component) {
|
if (!$component) {
|
||||||
return new JsonResponse(
|
return new JsonResponse(
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class CustomFieldValueController extends AbstractController
|
|||||||
#[Route('', name: 'custom_field_values_create', methods: ['POST'])]
|
#[Route('', name: 'custom_field_values_create', methods: ['POST'])]
|
||||||
public function create(Request $request): JsonResponse
|
public function create(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$payload = $this->decodePayload($request);
|
$payload = $this->decodePayload($request);
|
||||||
if ($payload instanceof JsonResponse) {
|
if ($payload instanceof JsonResponse) {
|
||||||
return $payload;
|
return $payload;
|
||||||
@@ -63,6 +65,8 @@ class CustomFieldValueController extends AbstractController
|
|||||||
#[Route('/upsert', name: 'custom_field_values_upsert', methods: ['POST'])]
|
#[Route('/upsert', name: 'custom_field_values_upsert', methods: ['POST'])]
|
||||||
public function upsert(Request $request): JsonResponse
|
public function upsert(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$payload = $this->decodePayload($request);
|
$payload = $this->decodePayload($request);
|
||||||
if ($payload instanceof JsonResponse) {
|
if ($payload instanceof JsonResponse) {
|
||||||
return $payload;
|
return $payload;
|
||||||
@@ -104,6 +108,8 @@ class CustomFieldValueController extends AbstractController
|
|||||||
#[Route('/{entityType}/{entityId}', name: 'custom_field_values_list', methods: ['GET'])]
|
#[Route('/{entityType}/{entityId}', name: 'custom_field_values_list', methods: ['GET'])]
|
||||||
public function listByEntity(string $entityType, string $entityId): JsonResponse
|
public function listByEntity(string $entityType, string $entityId): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$target = $this->resolveTarget([
|
$target = $this->resolveTarget([
|
||||||
'entityType' => $entityType,
|
'entityType' => $entityType,
|
||||||
'entityId' => $entityId,
|
'entityId' => $entityId,
|
||||||
@@ -126,6 +132,8 @@ class CustomFieldValueController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'custom_field_values_update', methods: ['PATCH'])]
|
#[Route('/{id}', name: 'custom_field_values_update', methods: ['PATCH'])]
|
||||||
public function update(string $id, Request $request): JsonResponse
|
public function update(string $id, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$value = $this->customFieldValueRepository->find($id);
|
$value = $this->customFieldValueRepository->find($id);
|
||||||
if (!$value instanceof CustomFieldValue) {
|
if (!$value instanceof CustomFieldValue) {
|
||||||
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
|
||||||
@@ -148,6 +156,8 @@ class CustomFieldValueController extends AbstractController
|
|||||||
#[Route('/{id}', name: 'custom_field_values_delete', methods: ['DELETE'])]
|
#[Route('/{id}', name: 'custom_field_values_delete', methods: ['DELETE'])]
|
||||||
public function delete(string $id): JsonResponse
|
public function delete(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$value = $this->customFieldValueRepository->find($id);
|
$value = $this->customFieldValueRepository->find($id);
|
||||||
if (!$value instanceof CustomFieldValue) {
|
if (!$value instanceof CustomFieldValue) {
|
||||||
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class DocumentQueryController extends AbstractController
|
|||||||
#[Route('/site/{id}', name: 'documents_by_site', methods: ['GET'])]
|
#[Route('/site/{id}', name: 'documents_by_site', methods: ['GET'])]
|
||||||
public function listBySite(string $id): JsonResponse
|
public function listBySite(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$site = $this->siteRepository->find($id);
|
$site = $this->siteRepository->find($id);
|
||||||
if (!$site) {
|
if (!$site) {
|
||||||
return $this->json(['success' => false, 'error' => 'Site not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Site not found.'], 404);
|
||||||
@@ -43,6 +45,8 @@ class DocumentQueryController extends AbstractController
|
|||||||
#[Route('/machine/{id}', name: 'documents_by_machine', methods: ['GET'])]
|
#[Route('/machine/{id}', name: 'documents_by_machine', methods: ['GET'])]
|
||||||
public function listByMachine(string $id): JsonResponse
|
public function listByMachine(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$machine = $this->machineRepository->find($id);
|
$machine = $this->machineRepository->find($id);
|
||||||
if (!$machine) {
|
if (!$machine) {
|
||||||
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
||||||
@@ -56,6 +60,8 @@ class DocumentQueryController extends AbstractController
|
|||||||
#[Route('/composant/{id}', name: 'documents_by_composant', methods: ['GET'])]
|
#[Route('/composant/{id}', name: 'documents_by_composant', methods: ['GET'])]
|
||||||
public function listByComposant(string $id): JsonResponse
|
public function listByComposant(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$composant = $this->composantRepository->find($id);
|
$composant = $this->composantRepository->find($id);
|
||||||
if (!$composant) {
|
if (!$composant) {
|
||||||
return $this->json(['success' => false, 'error' => 'Composant not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Composant not found.'], 404);
|
||||||
@@ -69,6 +75,8 @@ class DocumentQueryController extends AbstractController
|
|||||||
#[Route('/piece/{id}', name: 'documents_by_piece', methods: ['GET'])]
|
#[Route('/piece/{id}', name: 'documents_by_piece', methods: ['GET'])]
|
||||||
public function listByPiece(string $id): JsonResponse
|
public function listByPiece(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$piece = $this->pieceRepository->find($id);
|
$piece = $this->pieceRepository->find($id);
|
||||||
if (!$piece) {
|
if (!$piece) {
|
||||||
return $this->json(['success' => false, 'error' => 'Piece not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Piece not found.'], 404);
|
||||||
@@ -82,6 +90,8 @@ class DocumentQueryController extends AbstractController
|
|||||||
#[Route('/product/{id}', name: 'documents_by_product', methods: ['GET'])]
|
#[Route('/product/{id}', name: 'documents_by_product', methods: ['GET'])]
|
||||||
public function listByProduct(string $id): JsonResponse
|
public function listByProduct(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$product = $this->productRepository->find($id);
|
$product = $this->productRepository->find($id);
|
||||||
if (!$product) {
|
if (!$product) {
|
||||||
return $this->json(['success' => false, 'error' => 'Product not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Product not found.'], 404);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class MachineCustomFieldsController extends AbstractController
|
|||||||
#[Route('/{id}/add-custom-fields', name: 'machine_add_custom_fields', methods: ['POST'])]
|
#[Route('/{id}/add-custom-fields', name: 'machine_add_custom_fields', methods: ['POST'])]
|
||||||
public function addMissingCustomFields(string $id): JsonResponse
|
public function addMissingCustomFields(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$machine = $this->machineRepository->find($id);
|
$machine = $this->machineRepository->find($id);
|
||||||
if (!$machine instanceof Machine) {
|
if (!$machine instanceof Machine) {
|
||||||
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ use App\Repository\AuditLogRepository;
|
|||||||
use App\Repository\MachineRepository;
|
use App\Repository\MachineRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class MachineHistoryController
|
final class MachineHistoryController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MachineRepository $machines,
|
private readonly MachineRepository $machines,
|
||||||
@@ -23,6 +24,8 @@ final class MachineHistoryController
|
|||||||
#[Route('/api/machines/{id}/history', name: 'api_machine_history', methods: ['GET'])]
|
#[Route('/api/machines/{id}/history', name: 'api_machine_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$machine = $this->machines->find($id);
|
$machine = $this->machines->find($id);
|
||||||
if (!$machine) {
|
if (!$machine) {
|
||||||
return new JsonResponse(
|
return new JsonResponse(
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class MachineSkeletonController extends AbstractController
|
|||||||
#[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
|
#[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
|
||||||
public function getSkeleton(string $id): JsonResponse
|
public function getSkeleton(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$machine = $this->machineRepository->find($id);
|
$machine = $this->machineRepository->find($id);
|
||||||
if (!$machine instanceof Machine) {
|
if (!$machine instanceof Machine) {
|
||||||
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
||||||
@@ -73,6 +75,8 @@ class MachineSkeletonController extends AbstractController
|
|||||||
#[Route('/{id}/skeleton', name: 'machine_skeleton_update', methods: ['PATCH'])]
|
#[Route('/{id}/skeleton', name: 'machine_skeleton_update', methods: ['PATCH'])]
|
||||||
public function updateSkeleton(string $id, Request $request): JsonResponse
|
public function updateSkeleton(string $id, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$machine = $this->machineRepository->find($id);
|
$machine = $this->machineRepository->find($id);
|
||||||
if (!$machine instanceof Machine) {
|
if (!$machine instanceof Machine) {
|
||||||
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Repository\ModelTypeRepository;
|
use App\Repository\ModelTypeRepository;
|
||||||
use App\Service\ModelTypeCategoryConversionService;
|
use App\Service\ModelTypeCategoryConversionService;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class ModelTypeConversionController
|
final class ModelTypeConversionController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ModelTypeRepository $modelTypes,
|
private readonly ModelTypeRepository $modelTypes,
|
||||||
@@ -20,6 +21,8 @@ final class ModelTypeConversionController
|
|||||||
#[Route('/api/model_types/{id}/conversion-check', name: 'api_model_type_conversion_check', methods: ['GET'])]
|
#[Route('/api/model_types/{id}/conversion-check', name: 'api_model_type_conversion_check', methods: ['GET'])]
|
||||||
public function check(string $id): JsonResponse
|
public function check(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$modelType = $this->modelTypes->find($id);
|
$modelType = $this->modelTypes->find($id);
|
||||||
|
|
||||||
if (!$modelType) {
|
if (!$modelType) {
|
||||||
@@ -35,6 +38,8 @@ final class ModelTypeConversionController
|
|||||||
#[Route('/api/model_types/{id}/convert', name: 'api_model_type_convert', methods: ['POST'])]
|
#[Route('/api/model_types/{id}/convert', name: 'api_model_type_convert', methods: ['POST'])]
|
||||||
public function convert(string $id): JsonResponse
|
public function convert(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
||||||
|
|
||||||
$modelType = $this->modelTypes->find($id);
|
$modelType = $this->modelTypes->find($id);
|
||||||
|
|
||||||
if (!$modelType) {
|
if (!$modelType) {
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ use App\Repository\AuditLogRepository;
|
|||||||
use App\Repository\PieceRepository;
|
use App\Repository\PieceRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class PieceHistoryController
|
final class PieceHistoryController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PieceRepository $pieces,
|
private readonly PieceRepository $pieces,
|
||||||
@@ -23,6 +24,8 @@ final class PieceHistoryController
|
|||||||
#[Route('/api/pieces/{id}/history', name: 'api_piece_history', methods: ['GET'])]
|
#[Route('/api/pieces/{id}/history', name: 'api_piece_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$piece = $this->pieces->find($id);
|
$piece = $this->pieces->find($id);
|
||||||
if (!$piece) {
|
if (!$piece) {
|
||||||
return new JsonResponse(
|
return new JsonResponse(
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ use App\Repository\AuditLogRepository;
|
|||||||
use App\Repository\ProductRepository;
|
use App\Repository\ProductRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class ProductHistoryController
|
final class ProductHistoryController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ProductRepository $products,
|
private readonly ProductRepository $products,
|
||||||
@@ -23,6 +24,8 @@ final class ProductHistoryController
|
|||||||
#[Route('/api/products/{id}/history', name: 'api_product_history', methods: ['GET'])]
|
#[Route('/api/products/{id}/history', name: 'api_product_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
||||||
|
|
||||||
$product = $this->products->find($id);
|
$product = $this->products->find($id);
|
||||||
if (!$product) {
|
if (!$product) {
|
||||||
return new JsonResponse(
|
return new JsonResponse(
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ use App\Repository\ProfileRepository;
|
|||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class SessionProfileController
|
final class SessionProfileController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ProfileRepository $profiles) {}
|
public function __construct(
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
) {}
|
||||||
|
|
||||||
#[Route('/api/session/profile', name: 'api_session_profile_get', methods: ['GET'])]
|
#[Route('/api/session/profile', name: 'api_session_profile_get', methods: ['GET'])]
|
||||||
public function getActiveProfile(Request $request): JsonResponse
|
public function getActiveProfile(Request $request): JsonResponse
|
||||||
@@ -64,7 +68,24 @@ final class SessionProfileController
|
|||||||
return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED);
|
return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$password = $payload['password'] ?? '';
|
||||||
|
if ('' === $password) {
|
||||||
|
return new JsonResponse(['message' => 'Mot de passe requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$profile->getPassword()) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Ce profil n\'a pas de mot de passe. Contactez un administrateur.'],
|
||||||
|
JsonResponse::HTTP_FORBIDDEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->passwordHasher->isPasswordValid($profile, $password)) {
|
||||||
|
return new JsonResponse(['message' => 'Mot de passe incorrect.'], JsonResponse::HTTP_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
$session->set('profileId', $profile->getId());
|
$session->set('profileId', $profile->getId());
|
||||||
|
$session->set('profileRoles', $profile->getRoles());
|
||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'id' => $profile->getId(),
|
'id' => $profile->getId(),
|
||||||
|
|||||||
@@ -4,18 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\Profile;
|
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
final class SessionProfilesController
|
final class SessionProfilesController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ProfileRepository $profiles,
|
private readonly ProfileRepository $profiles,
|
||||||
private readonly EntityManagerInterface $entityManager
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/session/profiles', name: 'api_session_profiles_list', methods: ['GET'])]
|
#[Route('/api/session/profiles', name: 'api_session_profiles_list', methods: ['GET'])]
|
||||||
@@ -29,52 +25,13 @@ final class SessionProfilesController
|
|||||||
->getResult()
|
->getResult()
|
||||||
;
|
;
|
||||||
|
|
||||||
return new JsonResponse(array_map([$this, 'serializeProfile'], $items));
|
return new JsonResponse(array_map(static function ($profile): array {
|
||||||
}
|
return [
|
||||||
|
'id' => $profile->getId(),
|
||||||
#[Route('/api/session/profiles', name: 'api_session_profiles_create', methods: ['POST'])]
|
'firstName' => $profile->getFirstName(),
|
||||||
public function create(Request $request): JsonResponse
|
'lastName' => $profile->getLastName(),
|
||||||
{
|
'hasPassword' => null !== $profile->getPassword() && '' !== $profile->getPassword(),
|
||||||
$payload = $request->toArray();
|
];
|
||||||
$firstName = trim((string) ($payload['firstName'] ?? ''));
|
}, $items));
|
||||||
$lastName = trim((string) ($payload['lastName'] ?? ''));
|
|
||||||
|
|
||||||
if ('' === $firstName || '' === $lastName) {
|
|
||||||
return new JsonResponse(['message' => 'firstName et lastName sont requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
$profile = new Profile();
|
|
||||||
$profile->setFirstName($firstName);
|
|
||||||
$profile->setLastName($lastName);
|
|
||||||
$profile->setIsActive(true);
|
|
||||||
|
|
||||||
$this->entityManager->persist($profile);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
return new JsonResponse($this->serializeProfile($profile), JsonResponse::HTTP_CREATED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/session/profiles/{id}', name: 'api_session_profiles_delete', methods: ['DELETE'])]
|
|
||||||
public function delete(string $id): JsonResponse
|
|
||||||
{
|
|
||||||
$profile = $this->profiles->find($id);
|
|
||||||
if (!$profile) {
|
|
||||||
return new JsonResponse(['message' => 'Profil introuvable.'], JsonResponse::HTTP_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$profile->setIsActive(false);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
return new JsonResponse(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function serializeProfile(Profile $profile): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $profile->getId(),
|
|
||||||
'firstName' => $profile->getFirstName(),
|
|
||||||
'lastName' => $profile->getLastName(),
|
|
||||||
'isActive' => $profile->isActive(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
src/Entity/Comment.php
Normal file
235
src/Entity/Comment.php
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'comments')]
|
||||||
|
#[ORM\Index(columns: ['entity_type', 'entity_id', 'status'], name: 'idx_comment_entity_status')]
|
||||||
|
#[ORM\HasLifecycleCallbacks]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: ['entityType' => 'exact', 'entityId' => 'exact', 'status' => 'exact'])]
|
||||||
|
#[ApiFilter(OrderFilter::class, properties: ['createdAt'])]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
|
order: ['createdAt' => 'DESC'],
|
||||||
|
paginationClientItemsPerPage: true,
|
||||||
|
paginationMaximumItemsPerPage: 200
|
||||||
|
)]
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
|
private ?string $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT)]
|
||||||
|
private string $content;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 50, name: 'entity_type')]
|
||||||
|
private string $entityType;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 36, name: 'entity_id')]
|
||||||
|
private string $entityId;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true, name: 'entity_name')]
|
||||||
|
private ?string $entityName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 36, name: 'author_id')]
|
||||||
|
private string $authorId;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 255, name: 'author_name')]
|
||||||
|
private string $authorName;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 20)]
|
||||||
|
private string $status = 'open';
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 36, nullable: true, name: 'resolved_by_id')]
|
||||||
|
private ?string $resolvedById = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true, name: 'resolved_by_name')]
|
||||||
|
private ?string $resolvedByName = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true, name: 'resolved_at')]
|
||||||
|
private ?DateTimeImmutable $resolvedAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'created_at')]
|
||||||
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updated_at')]
|
||||||
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
|
#[ORM\PrePersist]
|
||||||
|
public function setCreatedAtValue(): void
|
||||||
|
{
|
||||||
|
$now = new DateTimeImmutable();
|
||||||
|
$this->createdAt = $now;
|
||||||
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
|
if (null === $this->id) {
|
||||||
|
$this->id = $this->generateCuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ORM\PreUpdate]
|
||||||
|
public function setUpdatedAtValue(): void
|
||||||
|
{
|
||||||
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContent(string $content): static
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityType(): string
|
||||||
|
{
|
||||||
|
return $this->entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEntityType(string $entityType): static
|
||||||
|
{
|
||||||
|
$this->entityType = $entityType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityId(): string
|
||||||
|
{
|
||||||
|
return $this->entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEntityId(string $entityId): static
|
||||||
|
{
|
||||||
|
$this->entityId = $entityId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityName(): ?string
|
||||||
|
{
|
||||||
|
return $this->entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEntityName(?string $entityName): static
|
||||||
|
{
|
||||||
|
$this->entityName = $entityName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthorId(): string
|
||||||
|
{
|
||||||
|
return $this->authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthorId(string $authorId): static
|
||||||
|
{
|
||||||
|
$this->authorId = $authorId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthorName(): string
|
||||||
|
{
|
||||||
|
return $this->authorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthorName(string $authorName): static
|
||||||
|
{
|
||||||
|
$this->authorName = $authorName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatus(): string
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(string $status): static
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResolvedById(): ?string
|
||||||
|
{
|
||||||
|
return $this->resolvedById;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResolvedById(?string $resolvedById): static
|
||||||
|
{
|
||||||
|
$this->resolvedById = $resolvedById;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResolvedByName(): ?string
|
||||||
|
{
|
||||||
|
return $this->resolvedByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResolvedByName(?string $resolvedByName): static
|
||||||
|
{
|
||||||
|
$this->resolvedByName = $resolvedByName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResolvedAt(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->resolvedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResolvedAt(?DateTimeImmutable $resolvedAt): static
|
||||||
|
{
|
||||||
|
$this->resolvedAt = $resolvedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,12 @@ use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
|||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\ComposantRepository;
|
use App\Repository\ComposantRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -22,6 +28,14 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeComposant' => 'exact'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeComposant' => 'exact'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
normalizationContext: ['groups' => ['composant:read']],
|
normalizationContext: ['groups' => ['composant:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
|
|||||||
@@ -5,17 +5,33 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\ConstructeurRepository;
|
use App\Repository\ConstructeurRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
|
|
||||||
|
#[UniqueEntity(fields: ['name'], message: 'Un fournisseur avec ce nom existe déjà.')]
|
||||||
#[ORM\Entity(repositoryClass: ConstructeurRepository::class)]
|
#[ORM\Entity(repositoryClass: ConstructeurRepository::class)]
|
||||||
#[ORM\Table(name: 'constructeurs')]
|
#[ORM\Table(name: 'constructeurs')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\CustomFieldRepository;
|
use App\Repository\CustomFieldRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -16,7 +22,16 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: CustomFieldRepository::class)]
|
#[ORM\Entity(repositoryClass: CustomFieldRepository::class)]
|
||||||
#[ORM\Table(name: 'custom_fields')]
|
#[ORM\Table(name: 'custom_fields')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class CustomField
|
class CustomField
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\CustomFieldValueRepository;
|
use App\Repository\CustomFieldValueRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -14,7 +20,16 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: CustomFieldValueRepository::class)]
|
#[ORM\Entity(repositoryClass: CustomFieldValueRepository::class)]
|
||||||
#[ORM\Table(name: 'custom_field_values')]
|
#[ORM\Table(name: 'custom_field_values')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class CustomFieldValue
|
class CustomFieldValue
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -21,11 +21,17 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new GetCollection(normalizationContext: ['groups' => ['document:list']]),
|
new GetCollection(
|
||||||
new Get(normalizationContext: ['groups' => ['document:list', 'document:detail']]),
|
security: "is_granted('ROLE_VIEWER')",
|
||||||
new Post(),
|
normalizationContext: ['groups' => ['document:list']],
|
||||||
new Put(),
|
),
|
||||||
new Delete(),
|
new Get(
|
||||||
|
security: "is_granted('ROLE_VIEWER')",
|
||||||
|
normalizationContext: ['groups' => ['document:list', 'document:detail']],
|
||||||
|
),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
],
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\MachineRepository;
|
use App\Repository\MachineRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -16,7 +22,16 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: MachineRepository::class)]
|
#[ORM\Entity(repositoryClass: MachineRepository::class)]
|
||||||
#[ORM\Table(name: 'machines')]
|
#[ORM\Table(name: 'machines')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class Machine
|
class Machine
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\MachineComponentLinkRepository;
|
use App\Repository\MachineComponentLinkRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -15,7 +21,16 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
#[ORM\Entity(repositoryClass: MachineComponentLinkRepository::class)]
|
#[ORM\Entity(repositoryClass: MachineComponentLinkRepository::class)]
|
||||||
#[ORM\Table(name: 'machine_component_links')]
|
#[ORM\Table(name: 'machine_component_links')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class MachineComponentLink
|
class MachineComponentLink
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\MachinePieceLinkRepository;
|
use App\Repository\MachinePieceLinkRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -15,7 +21,16 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
#[ORM\Entity(repositoryClass: MachinePieceLinkRepository::class)]
|
#[ORM\Entity(repositoryClass: MachinePieceLinkRepository::class)]
|
||||||
#[ORM\Table(name: 'machine_piece_links')]
|
#[ORM\Table(name: 'machine_piece_links')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class MachinePieceLink
|
class MachinePieceLink
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\MachineProductLinkRepository;
|
use App\Repository\MachineProductLinkRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -15,7 +21,16 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
#[ORM\Entity(repositoryClass: MachineProductLinkRepository::class)]
|
#[ORM\Entity(repositoryClass: MachineProductLinkRepository::class)]
|
||||||
#[ORM\Table(name: 'machine_product_links')]
|
#[ORM\Table(name: 'machine_product_links')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class MachineProductLink
|
class MachineProductLink
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
|||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Enum\ModelCategory;
|
use App\Enum\ModelCategory;
|
||||||
use App\Repository\ModelTypeRepository;
|
use App\Repository\ModelTypeRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
@@ -24,6 +30,14 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ApiFilter(SearchFilter::class, properties: ['category' => 'exact', 'name' => 'ipartial'])]
|
#[ApiFilter(SearchFilter::class, properties: ['category' => 'exact', 'name' => 'ipartial'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -8,20 +8,36 @@ use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
|||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\PieceRepository;
|
use App\Repository\PieceRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[UniqueEntity(fields: ['reference'], message: 'Une pièce avec cette référence existe déjà.')]
|
||||||
#[ORM\Entity(repositoryClass: PieceRepository::class)]
|
#[ORM\Entity(repositoryClass: PieceRepository::class)]
|
||||||
#[ORM\Table(name: 'pieces')]
|
#[ORM\Table(name: 'pieces')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typePiece' => 'exact'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typePiece' => 'exact'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
normalizationContext: ['groups' => ['piece:read']],
|
normalizationContext: ['groups' => ['piece:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
@@ -33,11 +49,11 @@ class Piece
|
|||||||
#[Groups(['piece:read', 'document:list'])]
|
#[Groups(['piece:read', 'document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
#[Groups(['piece:read', 'document:list'])]
|
#[Groups(['piece:read', 'document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, unique: true, nullable: true)]
|
||||||
#[Groups(['piece:read'])]
|
#[Groups(['piece:read'])]
|
||||||
private ?string $reference = null;
|
private ?string $reference = null;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
|||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\ProductRepository;
|
use App\Repository\ProductRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -22,6 +28,14 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeProduct' => 'exact'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeProduct' => 'exact'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
],
|
||||||
normalizationContext: ['groups' => ['product:read']],
|
normalizationContext: ['groups' => ['product:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ use ApiPlatform\Metadata\ApiResource;
|
|||||||
use ApiPlatform\Metadata\Delete;
|
use ApiPlatform\Metadata\Delete;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\Metadata\Put;
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
|
use App\State\ProfilePasswordHasher;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
@@ -24,11 +26,24 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(),
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
new GetCollection(),
|
new GetCollection(security: "is_granted('ROLE_ADMIN')"),
|
||||||
new Post(),
|
new Post(
|
||||||
new Put(),
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
new Delete(),
|
denormalizationContext: ['groups' => ['profile:write', 'profile:admin:write']],
|
||||||
|
processor: ProfilePasswordHasher::class,
|
||||||
|
),
|
||||||
|
new Put(
|
||||||
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
|
denormalizationContext: ['groups' => ['profile:write', 'profile:admin:write']],
|
||||||
|
processor: ProfilePasswordHasher::class,
|
||||||
|
),
|
||||||
|
new Patch(
|
||||||
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
|
denormalizationContext: ['groups' => ['profile:write', 'profile:admin:write']],
|
||||||
|
processor: ProfilePasswordHasher::class,
|
||||||
|
),
|
||||||
|
new Delete(security: "is_granted('ROLE_ADMIN')"),
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['profile:read']],
|
normalizationContext: ['groups' => ['profile:read']],
|
||||||
denormalizationContext: ['groups' => ['profile:write']]
|
denormalizationContext: ['groups' => ['profile:write']]
|
||||||
@@ -63,16 +78,21 @@ class Profile implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
* @var list<string> The user roles
|
* @var list<string> The user roles
|
||||||
*/
|
*/
|
||||||
#[ORM\Column(type: 'json', options: ['default' => '["ROLE_USER"]'])]
|
#[ORM\Column(type: 'json', options: ['default' => '["ROLE_USER"]'])]
|
||||||
#[Groups(['profile:read', 'profile:write'])]
|
#[Groups(['profile:read', 'profile:admin:write'])]
|
||||||
private array $roles = ['ROLE_USER'];
|
private array $roles = ['ROLE_USER'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The hashed password
|
* @var null|string The hashed password
|
||||||
*/
|
*/
|
||||||
#[ORM\Column(type: 'string', nullable: true)]
|
#[ORM\Column(type: 'string', nullable: true)]
|
||||||
#[Groups(['profile:write'])]
|
|
||||||
private ?string $password = null;
|
private ?string $password = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-persisted field used for password hashing via ProfilePasswordHasher.
|
||||||
|
*/
|
||||||
|
#[Groups(['profile:write'])]
|
||||||
|
private ?string $plainPassword = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable', name: 'createdat')]
|
#[ORM\Column(type: 'datetime_immutable', name: 'createdat')]
|
||||||
#[Groups(['profile:read'])]
|
#[Groups(['profile:read'])]
|
||||||
private DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
@@ -83,7 +103,6 @@ class Profile implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// Générer un CUID-like ID pour compatibilité avec Prisma
|
|
||||||
$this->id = 'cl'.substr(strtolower(base_convert(random_bytes(12), 2, 36)), 0, 24);
|
$this->id = 'cl'.substr(strtolower(base_convert(random_bytes(12), 2, 36)), 0, 24);
|
||||||
$this->createdAt = new DateTimeImmutable();
|
$this->createdAt = new DateTimeImmutable();
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
@@ -157,11 +176,10 @@ class Profile implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
*/
|
*/
|
||||||
public function getRoles(): array
|
public function getRoles(): array
|
||||||
{
|
{
|
||||||
$roles = $this->roles;
|
$roles = $this->roles;
|
||||||
// guarantee every user at least has ROLE_USER
|
|
||||||
$roles[] = 'ROLE_USER';
|
$roles[] = 'ROLE_USER';
|
||||||
|
|
||||||
return array_unique($roles);
|
return array_values(array_unique($roles));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,20 +200,37 @@ class Profile implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
return $this->password;
|
return $this->password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPassword(string $password): static
|
public function setPassword(?string $password): static
|
||||||
{
|
{
|
||||||
$this->password = $password;
|
$this->password = $password;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPlainPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->plainPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlainPassword(?string $plainPassword): static
|
||||||
|
{
|
||||||
|
$this->plainPassword = $plainPassword;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Groups(['profile:read'])]
|
||||||
|
public function getHasPassword(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->password && '' !== $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see UserInterface
|
* @see UserInterface
|
||||||
*/
|
*/
|
||||||
public function eraseCredentials(): void
|
public function eraseCredentials(): void
|
||||||
{
|
{
|
||||||
// If you store any temporary, sensitive data on the user, clear it here
|
$this->plainPassword = null;
|
||||||
// $this->plainPassword = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt(): DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(),
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
new GetCollection(),
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
new Post(),
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
new Put(),
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
new Delete(),
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
],
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
#[UniqueEntity(fields: ['name'], message: 'Ce nom de type de machine existe déjà.')]
|
#[UniqueEntity(fields: ['name'], message: 'Ce nom de type de machine existe déjà.')]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(),
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
new GetCollection(),
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
new Post(),
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
new Put(),
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
new Delete(),
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
],
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 200
|
paginationMaximumItemsPerPage: 200
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\TypeMachineComponentRequirementRepository;
|
use App\Repository\TypeMachineComponentRequirementRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -17,7 +23,16 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: TypeMachineComponentRequirementRepository::class)]
|
#[ORM\Entity(repositoryClass: TypeMachineComponentRequirementRepository::class)]
|
||||||
#[ORM\Table(name: 'type_machine_component_requirements')]
|
#[ORM\Table(name: 'type_machine_component_requirements')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class TypeMachineComponentRequirement
|
class TypeMachineComponentRequirement
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\TypeMachinePieceRequirementRepository;
|
use App\Repository\TypeMachinePieceRequirementRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -17,7 +23,16 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: TypeMachinePieceRequirementRepository::class)]
|
#[ORM\Entity(repositoryClass: TypeMachinePieceRequirementRepository::class)]
|
||||||
#[ORM\Table(name: 'type_machine_piece_requirements')]
|
#[ORM\Table(name: 'type_machine_piece_requirements')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class TypeMachinePieceRequirement
|
class TypeMachinePieceRequirement
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\TypeMachineProductRequirementRepository;
|
use App\Repository\TypeMachineProductRequirementRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -17,7 +23,16 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: TypeMachineProductRequirementRepository::class)]
|
#[ORM\Entity(repositoryClass: TypeMachineProductRequirementRepository::class)]
|
||||||
#[ORM\Table(name: 'type_machine_product_requirements')]
|
#[ORM\Table(name: 'type_machine_product_requirements')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
|
||||||
|
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
|
||||||
|
]
|
||||||
|
)]
|
||||||
class TypeMachineProductRequirement
|
class TypeMachineProductRequirement
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
65
src/Security/SessionProfileAuthenticator.php
Normal file
65
src/Security/SessionProfileAuthenticator.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||||
|
|
||||||
|
final class SessionProfileAuthenticator extends AbstractAuthenticator
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supports(Request $request): ?bool
|
||||||
|
{
|
||||||
|
if (!$request->hasSession()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->getSession()->has('profileId');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticate(Request $request): Passport
|
||||||
|
{
|
||||||
|
$profileId = $request->getSession()->get('profileId');
|
||||||
|
|
||||||
|
return new SelfValidatingPassport(
|
||||||
|
new UserBadge($profileId, function (string $id): Profile {
|
||||||
|
$profile = $this->profiles->find($id);
|
||||||
|
|
||||||
|
if (!$profile || !$profile->isActive()) {
|
||||||
|
throw new CustomUserMessageAuthenticationException('Profil introuvable ou inactif.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $profile;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||||
|
{
|
||||||
|
// Let the request continue normally
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||||
|
{
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => $exception->getMessageKey()],
|
||||||
|
JsonResponse::HTTP_UNAUTHORIZED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/State/ProfilePasswordHasher.php
Normal file
36
src/State/ProfilePasswordHasher.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\State;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProcessorInterface;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
|
||||||
|
final class ProfilePasswordHasher implements ProcessorInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||||
|
private readonly ProcessorInterface $decorated,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $uriVariables
|
||||||
|
* @param array<string, mixed> $context
|
||||||
|
*/
|
||||||
|
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||||
|
{
|
||||||
|
if ($data instanceof Profile && $data->getPlainPassword()) {
|
||||||
|
$data->setPassword(
|
||||||
|
$this->passwordHasher->hashPassword($data, $data->getPlainPassword())
|
||||||
|
);
|
||||||
|
$data->eraseCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->decorated->process($data, $operation, $uriVariables, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user