Compare commits
15 Commits
c81142e4a5
...
c3d3218bff
| Author | SHA1 | Date |
|---|---|---|
|
|
c3d3218bff | |
|
|
f24fb0180d | |
|
|
e360019e58 | |
|
|
8e38fe47db | |
|
|
b54fe41795 | |
|
|
798ca4ba07 | |
|
|
d43b516826 | |
|
|
c99b575814 | |
|
|
65ff838dd9 | |
|
|
4a2f9d9547 | |
|
|
10a8eb2255 | |
|
|
8f232498b8 | |
|
|
bbe50dbfd9 | |
|
|
436284c9c7 | |
|
|
228ef8cbe9 |
|
|
@ -141,7 +141,6 @@
|
|||
<path value="$PROJECT_DIR$/vendor/defuse/php-encryption" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/ux-turbo" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/stimulus-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/console" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 464a208 208 0 1 1 0-416a208 208 0 1 1 0 416m0-464a256 256 0 1 0 0 512a256 256 0 1 0 0-512m120.9 294.6c4.5-4.2 7.1-10.1 7.1-16.3c0-12.3-10-22.3-22.3-22.3H304v-96c0-17.7-14.3-32-32-32h-32c-17.7 0-32 14.3-32 32v96h-57.7c-12.3 0-22.3 10-22.3 22.3c0 6.2 2.6 12.1 7.1 16.3l107.1 99.9c3.8 3.5 8.7 5.5 13.8 5.5s10.1-2 13.8-5.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 425 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 48a208 208 0 1 1 0 416a208 208 0 1 1 0-416m0 464a256 256 0 1 0 0-512a256 256 0 1 0 0 512M151.2 217.4c-4.6 4.2-7.2 10.1-7.2 16.4c0 12.3 10 22.3 22.3 22.3H208v96c0 17.7 14.3 32 32 32h32c17.7 0 32-14.3 32-32v-96h41.7c12.3 0 22.3-10 22.3-22.3c0-6.2-2.6-12.1-7.2-16.4l-91-84c-3.8-3.5-8.7-5.4-13.9-5.4s-10.1 1.9-13.9 5.4l-91 84z"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M406.5 399.6c-19.1-46.7-65-79.6-118.5-79.6h-64c-53.5 0-99.4 32.9-118.5 79.6C69.9 362.2 48 311.7 48 256c0-114.9 93.1-208 208-208s208 93.1 208 208c0 55.7-21.9 106.2-57.5 143.6m-40.1 32.7c-32 20.1-69.8 31.7-110.4 31.7s-78.4-11.6-110.5-31.7c7.3-36.7 39.7-64.3 78.5-64.3h64c38.8 0 71.2 27.6 78.5 64.3zM256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m0-272a40 40 0 1 1 0-80a40 40 0 1 1 0 80m-88-40a88 88 0 1 0 176 0a88 88 0 1 0-176 0"/></svg>
|
||||
|
After Width: | Height: | Size: 528 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M288 80c-65.2 0-118.8 29.6-159.9 67.7C89.6 183.5 63 226 49.4 256C63 286 89.6 328.5 128 364.3c41.2 38.1 94.8 67.7 160 67.7s118.8-29.6 159.9-67.7C486.4 328.5 513 286 526.6 256c-13.6-30-40.2-72.5-78.6-108.3C406.8 109.6 353.2 80 288 80M95.4 112.6C142.5 68.8 207.2 32 288 32s145.5 36.8 192.6 80.6c46.8 43.5 78.1 95.4 93 131.1c3.3 7.9 3.3 16.7 0 24.6c-14.9 35.7-46.2 87.7-93 131.1C433.5 443.2 368.8 480 288 480s-145.5-36.8-192.6-80.6C48.6 356 17.3 304 2.5 268.3c-3.3-7.9-3.3-16.7 0-24.6C17.3 208 48.6 156 95.4 112.6M288 336c44.2 0 80-35.8 80-80s-35.8-80-80-80h-2c1.3 5.1 2 10.5 2 16c0 35.3-28.7 64-64 64c-5.5 0-10.9-.7-16-2v2c0 44.2 35.8 80 80 80m0-208a128 128 0 1 1 0 256a128 128 0 1 1 0-256"/></svg>
|
||||
|
After Width: | Height: | Size: 787 B |
|
|
@ -1,3 +1,13 @@
|
|||
/*variable*/
|
||||
:root{
|
||||
--primary-blue-light : #086572;
|
||||
--primary-blue-dark : #094754;
|
||||
--black-font: #1D1E1C;
|
||||
--delete : #E42E31;
|
||||
--disable : #A3A3A3;
|
||||
--check : #80F20E;
|
||||
}
|
||||
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -77,4 +87,30 @@ body {
|
|||
font-family: "Nunito", sans-serif;
|
||||
font-weight: 400;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.btn-primary{
|
||||
background: var(--primary-blue-light);
|
||||
color : #FFFFFF;
|
||||
border: var(--primary-blue-dark);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.btn-primary:hover{
|
||||
background: var(--primary-blue-dark);
|
||||
color : #FFFFFF;
|
||||
border: var(--primary-blue-light);
|
||||
}
|
||||
|
||||
.btn-danger{
|
||||
background: var(--delete);
|
||||
color : #FFFFFF;
|
||||
border: var(--delete);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.color-primary{
|
||||
color: var(--primary-blue-light);
|
||||
}
|
||||
.color-primary-dark{
|
||||
color: var(--primary-blue-dark);
|
||||
}
|
||||
|
|
@ -154,99 +154,6 @@
|
|||
},
|
||||
"time": "2023-06-19T06:10:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/cache.git",
|
||||
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
|
||||
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": ">2.2,<2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"cache/integration-tests": "dev-master",
|
||||
"doctrine/coding-standard": "^9",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"psr/cache": "^1.0 || ^2.0 || ^3.0",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6",
|
||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/cache.html",
|
||||
"keywords": [
|
||||
"abstraction",
|
||||
"apcu",
|
||||
"cache",
|
||||
"caching",
|
||||
"couchdb",
|
||||
"memcached",
|
||||
"php",
|
||||
"redis",
|
||||
"xcache"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/cache/issues",
|
||||
"source": "https://github.com/doctrine/cache/tree/2.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-20T20:07:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
"version": "2.3.0",
|
||||
|
|
@ -335,28 +242,31 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.9.5",
|
||||
"version": "3.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868"
|
||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/4a4e2eed3134036ee36a147ee0dac037dfa17868",
|
||||
"reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/deprecations": "^0.5.3|^1",
|
||||
"doctrine/event-manager": "^1|^2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1|^2|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/cache": "< 1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/coding-standard": "13.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
|
|
@ -426,7 +336,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.9.5"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -442,7 +352,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-15T22:40:05+00:00"
|
||||
"time": "2025-07-10T21:11:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ security:
|
|||
property: email
|
||||
|
||||
role_hierarchy:
|
||||
ROLE_ADMIN: ROLE_USER
|
||||
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
|
||||
ROLE_SUDALYS: ROLE_USER
|
||||
ROLE_ADMIN: ROLE_USER
|
||||
ROLE_SUDALYS_ADMIN: [ROLE_SUDALYS, ROLE_ALLOWED_TO_SWITCH, ROLE_ADMIN]
|
||||
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709072959 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP SEQUENCE subscriptions_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE subscriptions DROP CONSTRAINT fk_4778a0167b3b43d');
|
||||
$this->addSql('DROP TABLE subscriptions');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('CREATE SEQUENCE subscriptions_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE subscriptions (id SERIAL NOT NULL, users_id INT NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX idx_4778a0167b3b43d ON subscriptions (users_id)');
|
||||
$this->addSql('ALTER TABLE subscriptions ADD CONSTRAINT fk_4778a0167b3b43d FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709073312 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE subscriptions (id SERIAL NOT NULL, users_id INT NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_4778A0167B3B43D ON subscriptions (users_id)');
|
||||
$this->addSql('ALTER TABLE subscriptions ADD CONSTRAINT FK_4778A0167B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE subscriptions DROP CONSTRAINT FK_4778A0167B3B43D');
|
||||
$this->addSql('DROP TABLE subscriptions');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709073752 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE user_tab (id SERIAL NOT NULL, users_id INT NOT NULL, ip_address VARCHAR(255) NOT NULL, tab_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_98F5228767B3B43D ON user_tab (users_id)');
|
||||
$this->addSql('ALTER TABLE user_tab ADD CONSTRAINT FK_98F5228767B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE user_tab DROP CONSTRAINT FK_98F5228767B3B43D');
|
||||
$this->addSql('DROP TABLE user_tab');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709115309 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP SEQUENCE subscriptions_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE subscriptions DROP CONSTRAINT fk_4778a0167b3b43d');
|
||||
$this->addSql('DROP TABLE subscriptions');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('CREATE SEQUENCE subscriptions_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE subscriptions (id SERIAL NOT NULL, users_id INT NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX idx_4778a0167b3b43d ON subscriptions (users_id)');
|
||||
$this->addSql('ALTER TABLE subscriptions ADD CONSTRAINT fk_4778a0167b3b43d FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709120951 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE organizations ADD name VARCHAR(255) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE organizations DROP name');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709121023 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE organizations ADD name VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE organizations DROP name');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250709141934 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD last_connection DATE DEFAULT NULL');
|
||||
$this->addSql('COMMENT ON COLUMN "user".last_connection IS \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE "user" DROP last_connection');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250710070735 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER last_connection TYPE DATE');
|
||||
$this->addSql('COMMENT ON COLUMN "user".last_connection IS NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE "user" ALTER last_connection TYPE DATE');
|
||||
$this->addSql('COMMENT ON COLUMN "user".last_connection IS \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250710071344 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER last_connection TYPE TIMESTAMP(0) WITHOUT TIME ZONE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE "user" ALTER last_connection TYPE DATE');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250710090534 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD phone_number VARCHAR(20) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE "user" DROP phone_number');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250716083017 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE users_organizations ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP');
|
||||
$this->addSql('COMMENT ON COLUMN users_organizations.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE users_organizations DROP created_at');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250716130850 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\UserForm;
|
||||
use App\Service\UserOrganizationService;
|
||||
use App\Service\UserService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
#[Route(path: '/user', name: 'user_')]
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
private const NOT_FOUND = 'User not found';
|
||||
public function __construct(
|
||||
private readonly UserOrganizationService $userOrganizationService,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserService $userService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user - List all users (index/collection)
|
||||
*/
|
||||
#[Route('/', name: 'index', methods: ['GET'])]
|
||||
public function index(EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isGranted('ROLE_SUDALYS_ADMIN')) {
|
||||
$users = $entityManager->getRepository(User::class)->getAllActiveUsers();
|
||||
} else {
|
||||
$users = 'Not Super Admin';
|
||||
}
|
||||
return $this->render('user/index.html.twig', [
|
||||
'users' => $users,
|
||||
'controller_name' => 'IndexController',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/{id} - Show specific user (show/member)
|
||||
*/
|
||||
#[Route('/{id}', name: 'show', methods: ['GET'], requirements: ['id' => '\d+'])]
|
||||
public function show(int $id, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if (!$this->isGranted('ROLE_SUDALYS_ADMIN')) {
|
||||
throw $this->createAccessDeniedException('Access denied');
|
||||
}
|
||||
|
||||
$user = $entityManager->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
|
||||
$userOrganizations = $this->userOrganizationService->getUserOrganizations($user);
|
||||
|
||||
return $this->render('user/profile.html.twig', [
|
||||
'user' => $user,
|
||||
'userOrganizations' => $userOrganizations,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/new - Show form to create new user and handle submission
|
||||
*/
|
||||
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
|
||||
public function new(Request $request): Response
|
||||
{
|
||||
$form = $this->createForm(UserForm::class);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Data is a User object. App\Form\NewUserForm is a form type that maps to User entity
|
||||
$data = $form->getData();
|
||||
// Handle user creation logic here
|
||||
|
||||
//FOR DEV PURPOSES ONLY
|
||||
$data->setPictureUrl("");
|
||||
$data->setPassword($this->userService->generateRandomPassword());
|
||||
//FOR DEV PURPOSES ONLY
|
||||
|
||||
$this->entityManager->persist($data);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Redirect to user index
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
|
||||
return $this->render('user/new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/{id}/edit - Show form to edit user
|
||||
*/
|
||||
#[Route('/{id}/edit', name: 'edit', requirements: ['id' => '\d+'], methods: ['GET', 'PUT', 'POST'])]
|
||||
public function edit(int $id, EntityManagerInterface $entityManager, Request $request): Response
|
||||
{
|
||||
//Handle access control
|
||||
if (!$this->isGranted('ROLE_SUDALYS_ADMIN')) {
|
||||
throw $this->createAccessDeniedException('Access denied');
|
||||
}
|
||||
|
||||
//Fetch user by ID and handle not found case
|
||||
$user = $entityManager->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
|
||||
//Create form for editing user
|
||||
$form = $this->createForm(UserForm::class, $user);
|
||||
|
||||
//Handle form submission
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Persist changes to the user entity
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
//Redirect to user profile after successful edit
|
||||
return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
//
|
||||
// /**
|
||||
// * DELETE /user/{id} - Delete user
|
||||
// */
|
||||
// #[Route('/{id}', name: 'delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
|
||||
// public function delete(int $id, EntityManagerInterface $entityManager): Response
|
||||
// {
|
||||
// if (!$this->isGranted('ROLE_SUDALYS_ADMIN')) {
|
||||
// throw $this->createAccessDeniedException('Access denied');
|
||||
// }
|
||||
//
|
||||
// $user = $entityManager->getRepository(User::class)->find($id);
|
||||
// if (!$user) {
|
||||
// throw $this->createNotFoundException(self::NOT_FOUND));
|
||||
// }
|
||||
//
|
||||
// // Handle user deletion logic
|
||||
// $entityManager->remove($user);
|
||||
// $entityManager->flush();
|
||||
//
|
||||
// return $this->redirectToRoute('user_index');
|
||||
// }
|
||||
|
||||
|
||||
#[Route('/deactivate/{id}', name: 'deactivate', methods: ['GET'])]
|
||||
public function userDeactivate(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isGranted('ROLE_SUDALYS_ADMIN')) {
|
||||
$userId = $request->attributes->get('id');
|
||||
$user = $entityManager->getRepository(User::class)->find($userId);
|
||||
if (!$user) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
$user->setIsActive(false);
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
return new Response('Unauthorized', Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -42,6 +42,9 @@ class Organizations
|
|||
#[ORM\ManyToMany(targetEntity: Apps::class, mappedBy: 'organization')]
|
||||
private Collection $apps;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $name = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apps = new ArrayCollection();
|
||||
|
|
@ -162,4 +165,16 @@ class Organizations
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\SubscriptionsRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: SubscriptionsRepository::class)]
|
||||
class Subscriptions
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'subscriptions')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $users = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsers(): ?User
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function setUsers(?User $users): static
|
||||
{
|
||||
$this->users = $users;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,13 @@ namespace App\Entity;
|
|||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
|
||||
#[UniqueEntity(fields: ['email'], message: 'This email address is already in use.')]
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\Table(name: '`user`')]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
|
||||
|
|
@ -44,7 +47,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $picture_url = null;
|
||||
private ?string $pictureUrl = null;
|
||||
|
||||
#[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'])]
|
||||
private ?\DateTimeImmutable $modifiedAt = null;
|
||||
|
|
@ -55,17 +58,21 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
#[ORM\Column(options: ['default' => false])]
|
||||
private ?bool $isDeleted = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Subscriptions>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Subscriptions::class, mappedBy: 'users')]
|
||||
private Collection $subscriptions;
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?\DateTime $lastConnection = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $phoneNumber = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->subscriptions = new ArrayCollection();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->modifiedAt = new \DateTimeImmutable();
|
||||
$this->isActive = true;
|
||||
$this->isDeleted = false;
|
||||
}
|
||||
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
|
@ -179,12 +186,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
|
||||
public function getPictureUrl(): ?string
|
||||
{
|
||||
return $this->picture_url;
|
||||
return $this->pictureUrl;
|
||||
}
|
||||
|
||||
public function setPictureUrl(string $picture_url): static
|
||||
public function setPictureUrl(string $pictureUrl): static
|
||||
{
|
||||
$this->picture_url = $picture_url;
|
||||
$this->pictureUrl = $pictureUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -233,33 +240,29 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
return (string) $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Subscriptions>
|
||||
*/
|
||||
public function getSubscriptions(): Collection
|
||||
public function getLastConnection(): ?\DateTime
|
||||
{
|
||||
return $this->subscriptions;
|
||||
return $this->lastConnection;
|
||||
}
|
||||
|
||||
public function addSubscription(Subscriptions $subscription): static
|
||||
public function setLastConnection(?\DateTime $lastConnection): static
|
||||
{
|
||||
if (!$this->subscriptions->contains($subscription)) {
|
||||
$this->subscriptions->add($subscription);
|
||||
$subscription->setUsers($this);
|
||||
}
|
||||
$this->lastConnection = $lastConnection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeSubscription(Subscriptions $subscription): static
|
||||
public function getPhoneNumber(): ?string
|
||||
{
|
||||
if ($this->subscriptions->removeElement($subscription)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($subscription->getUsers() === $this) {
|
||||
$subscription->setUsers(null);
|
||||
}
|
||||
}
|
||||
return $this->phoneNumber;
|
||||
}
|
||||
|
||||
public function setPhoneNumber(?string $phoneNumber): static
|
||||
{
|
||||
$this->phoneNumber = $phoneNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,21 +21,24 @@ class UsersOrganizations
|
|||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?organizations $organization = null;
|
||||
private ?Organizations $organization = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?roles $role = null;
|
||||
private ?Roles $role = null;
|
||||
|
||||
#[ORM\Column(options: ['default' => true])]
|
||||
private ?bool $isActive = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, apps>
|
||||
* @var Collection<int, Apps>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: apps::class)]
|
||||
#[ORM\ManyToMany(targetEntity: Apps::class)]
|
||||
private Collection $apps;
|
||||
|
||||
#[ORM\Column(nullable:true, options: ['default' => 'CURRENT_TIMESTAMP'])]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apps = new ArrayCollection();
|
||||
|
|
@ -117,4 +120,16 @@ class UsersOrganizations
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?\DateTimeImmutable $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
|
||||
class LoginSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
LoginSuccessEvent::class => 'onLoginSuccess',
|
||||
];
|
||||
}
|
||||
|
||||
public function onLoginSuccess(LoginSuccessEvent $event): void
|
||||
{
|
||||
$user = $event->getUser();
|
||||
if($user) {
|
||||
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $user->getUserIdentifier()]);
|
||||
$user->setLastConnection(new \DateTime('now', new \DateTimeZone('Europe/Paris')));
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Service\ClientService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\Bundle\OAuth2ServerBundle\Event\ScopeResolveEvent;
|
||||
use League\Bundle\OAuth2ServerBundle\Repository\ScopeRepository;
|
||||
|
|
@ -16,15 +15,14 @@ final class ScopeResolveListener implements EventSubscriberInterface
|
|||
{
|
||||
private ClientRepositoryInterface $clientRepository;
|
||||
private LoggerInterface $logger;
|
||||
private ClientService $clientService;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(ClientRepositoryInterface $clientRepository, LoggerInterface $logger, ClientService $clientService, EntityManagerInterface $entityManager)
|
||||
public function __construct(ClientRepositoryInterface $clientRepository, LoggerInterface $logger, EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
// Inject the client repository
|
||||
$this->clientRepository = $clientRepository;
|
||||
$this->clientService = $clientService;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserForm extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, ['required' => true, 'label' => 'Email*'])
|
||||
->add('name', TextType::class, ['required' => true, 'label' => 'Prénom*'])
|
||||
->add('surname', TextType::class, ['required' => true, 'label' => 'Nom*'])
|
||||
->add('phoneNumber', TextType::class, ['required' => false, 'label' => 'Numéro de téléphone']);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,28 +33,13 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
|
|||
$this->getEntityManager()->flush();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return User[] Returns an array of User objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('u.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?User
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
public function getAllActiveUsers(): array{
|
||||
$queryBuilder = $this->createQueryBuilder('u')
|
||||
->select('u.surname', 'u.email', 'u.id', 'u.isActive', 'u.name', 'u.pictureUrl')
|
||||
// Remove this line: ->from(User::class, 'u')
|
||||
->where('u.isActive = :isActive') // Also fixed the concatenation
|
||||
->orderBy('u.surname', 'ASC');
|
||||
$queryBuilder->setParameter('isActive', true);
|
||||
return $queryBuilder->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ use App\Entity\UsersOrganizations;
|
|||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UsersOrganizations>
|
||||
*/
|
||||
|
||||
class UsersOrganizationsRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
|
|
@ -16,28 +14,18 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
|
|||
parent::__construct($registry, UsersOrganizations::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return UsersOrganizations[] Returns an array of UsersOrganizations objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('u.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
public function findAllDistinctOrganizationsByUserId(int $userId): array
|
||||
{
|
||||
return $this->createQueryBuilder('uo')
|
||||
->select('DISTINCT uo')
|
||||
->leftJoin('uo.organization', 'o')
|
||||
->leftJoin('uo.role', 'r')
|
||||
->addSelect('o', 'r')
|
||||
->where('uo.users = :userId', 'uo.isActive = :isActive')
|
||||
->setParameter('userId', $userId)
|
||||
->setParameter('isActive', true)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
// public function findOneBySomeField($value): ?UsersOrganizations
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
readonly class UserOrganizationService
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager) {}
|
||||
|
||||
/**
|
||||
* Returns all organizations the given user belongs to,
|
||||
* including the unique roles and apps the user has in each organization.
|
||||
*
|
||||
* @param User $user The user whose organizations are being fetched
|
||||
* @return array<array{organization:object, roles:Roles[], apps:object[]}>
|
||||
*/
|
||||
public function getUserOrganizations(User $user): array
|
||||
{
|
||||
$userOrganizations = $this->entityManager
|
||||
->getRepository(UsersOrganizations::class)
|
||||
->findAllDistinctOrganizationsByUserId($user->getId());
|
||||
$organizations = [];
|
||||
|
||||
foreach ($userOrganizations as $uo) {
|
||||
$orgId = $uo->getOrganization()->getId();
|
||||
|
||||
// Initialize the organization entry if it doesn't exist
|
||||
$organizations[$orgId] = $organizations[$orgId] ?? $this->createEmptyOrganizationBucket($uo);
|
||||
|
||||
// Aggregate roles & apps
|
||||
$this->addRole($organizations[$orgId]['roles'], $uo->getRole());
|
||||
$this->addApps($organizations[$orgId]['apps'], $uo->getApps());
|
||||
}
|
||||
|
||||
$this->normalizeAppsIndexes($organizations);
|
||||
|
||||
return array_values($organizations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the initial array structure for a fresh organization entry.
|
||||
*
|
||||
* @param UsersOrganizations $link
|
||||
* @return array{organization:object, roles:Roles[], apps:array<int,object>}
|
||||
*/
|
||||
private function createEmptyOrganizationBucket(UsersOrganizations $link): array
|
||||
{
|
||||
return [
|
||||
'organization' => $link->getOrganization(),
|
||||
'roles' => [],
|
||||
'apps' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Role entity to the roles array only if it is not already present (by ID).
|
||||
*
|
||||
* @param Roles[] &$roles
|
||||
* @param Roles|null $role
|
||||
*/
|
||||
private function addRole(array &$roles, ?Roles $role): void
|
||||
{
|
||||
if ($role === null) {
|
||||
return;
|
||||
}
|
||||
foreach ($roles as $existingRole) {
|
||||
if ($existingRole->getId() === $role->getId()) {
|
||||
return; // Already present
|
||||
}
|
||||
}
|
||||
$roles[] = $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge one or many apps into the apps map, keeping only one entry per id.
|
||||
*
|
||||
* @param array<int,object> &$apps
|
||||
* @param iterable $appsToAdd Collection returned by $userOrganizations->getApps()
|
||||
*/
|
||||
private function addApps(array &$apps, iterable $appsToAdd): void
|
||||
{
|
||||
foreach ($appsToAdd as $app) {
|
||||
$apps[$app->getId()] = $apps[$app->getId()] ?? $app;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert apps from associative maps (keyed by id) to plain indexed arrays,
|
||||
* so the final output is clean JSON-able.
|
||||
*
|
||||
* @param array &$organizations
|
||||
*/
|
||||
private function normalizeAppsIndexes(array &$organizations): void
|
||||
{
|
||||
foreach ($organizations as &$org) {
|
||||
$org['apps'] = array_values($org['apps']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
class UserService
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Constructor logic if needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random password for a new user until they set their own.
|
||||
*/
|
||||
public function generateRandomPassword(): string{
|
||||
$length = 50; // Length of the password
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomPassword = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomPassword .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
|
||||
return $randomPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Page Not Found (404){% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-center align-items-center vh-100">
|
||||
<h1>Oops! La page n'existe pas (404)</h1>
|
||||
<p>La page que vous cherchez n'existe pas.</p>
|
||||
<a href="{{ path('app_index') }}"> Retour à l'accueil </a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -22,11 +22,14 @@
|
|||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{# if user is Super Admin#}
|
||||
{% if is_granted('ROLE_SUDALYS_ADMIN') %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<a class="nav-link" href="{{ path('user_index') }}">
|
||||
<i class="icon-grid menu-icon">{{ ux_icon('bi:menu-up', {height: '15px', width: '15px'}) }}</i>
|
||||
<span class="menu-title">Menu 2</span>
|
||||
<span class="menu-title">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title shadow-sm p-3 d-flex justify-content-between align-items-center">
|
||||
<h2>{{ user.surname|capitalize }} {{ user.name|capitalize }}</h2>
|
||||
<a href="{{ path('user_edit', {'id': user.id}) }}" class="btn btn-primary">Modifier</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p> <b>Email: </b>{{ user.email }}</p>
|
||||
<p><b>Dernière connection: </b>{{ user.lastConnection|date('d/m/Y') }} à {{ user.lastConnection|date('H:m:s') }} </p>
|
||||
<p><b>Compte crée le: </b>{{ user.createdAt|date('d/m/Y') }}</p>
|
||||
<p><b>Numéro de téléphone: </b>{{ user.phoneNumber ? user.phoneNumber : 'Non renseigné' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{% block body %}
|
||||
|
||||
|
||||
<div class="card col-4 mt-3 me-3 user-org-card" style="cursor:pointer;" data-bs-toggle="collapse"
|
||||
data-bs-target="#org-details-{{ organization.id }}" aria-expanded="false"
|
||||
aria-controls="org-details-{{ organization.id }}">
|
||||
|
||||
<div class="card-title shadow-sm p-3 d-flex ">
|
||||
<h2 class=" pe-2">{{ organization.name|capitalize }}</h2>
|
||||
<i class="pt-2" id="arrow-icon-{{ organization.id }}">
|
||||
{{ ux_icon('fa6-regular:circle-down', {height: '25px', width: '25px'}) }}
|
||||
</i>
|
||||
</div>
|
||||
|
||||
{# Information principale sur l'utilisateur dans l'organisation#}
|
||||
<div class="card-body">
|
||||
{# Affichage du plus haut role #}
|
||||
<p><b>Role:</b>
|
||||
{% if roles|length > 0 %}
|
||||
{% set firstRole = roles[0] %}
|
||||
{% if firstRole.name == "ROLE ADMIN SUDALYS" or firstRole.name == "ROLE ADMIN" %}
|
||||
<span class="badge bg-danger">{{ firstRole.name|capitalize }}</span>
|
||||
{% elseif firstRole.name == "ROLE USER" %}
|
||||
<span class="badge bg-primary">{{ firstRole.name|capitalize }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">{{ firstRole.name|capitalize }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Aucun rôle
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{# Affichage des applications dont l'utilisateur à accès #}
|
||||
<div class="d-flex">
|
||||
{% if apps is not empty %}
|
||||
{% for app in apps %}
|
||||
<div class="col">
|
||||
{{ app.name }}
|
||||
<img src="{{ asset(app.logoUrl) }}" alt="Logo {{ app.name }}" class="img-fluid ms-2" style="height: 20px; width: 20px;">
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Aucune application associée.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Détails supplémentaires sur l'organisation #}
|
||||
<div class="collapse card-body border-top" id="org-details-{{ organization.id }}">
|
||||
{% if roles|length > 1 %}
|
||||
<p><b>Autres rôles:</b>
|
||||
{% for role in roles|slice(1) %}
|
||||
{% if role.name == "ROLE ADMIN SUDALYS" or role.name == "ROLE ADMIN" %}
|
||||
<span class="badge bg-danger">{{ role.name|capitalize }}</span>
|
||||
{% elseif role.name == "ROLE USER" %}
|
||||
<span class="badge bg-primary">{{ role.name|capitalize }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">{{ role.name|capitalize }}</span>
|
||||
{% endif %}
|
||||
{% if not loop.last %} - {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p><b>Membre depuis:</b> {{ organization.createdAt|date('d/m/Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var collapseEl = document.getElementById('org-details-{{ organization.id }}');
|
||||
var arrowEl = document.getElementById('arrow-icon-{{ organization.id }}');
|
||||
if (collapseEl && arrowEl) {
|
||||
collapseEl.addEventListener('show.bs.collapse', function () {
|
||||
arrowEl.innerHTML = `{{ ux_icon('fa6-regular:circle-up', {height: '25px', width: '25px'})|e('js') }}`;
|
||||
});
|
||||
collapseEl.addEventListener('hide.bs.collapse', function () {
|
||||
arrowEl.innerHTML = `{{ ux_icon('fa6-regular:circle-down', {height: '25px', width: '25px'})|e('js') }}`;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-100 h-100 p-5 m-auto">
|
||||
<h1>Modifier l'utilisateur</h1>
|
||||
{{ form_start(form, {'action': path('user_edit', {'id': user.id}), 'method': 'PUT'}) }}
|
||||
{{ form_widget(form) }}
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}User Profile{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-100 h-100 p-5 m-auto ">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Gestion Utilisateurs</h1>
|
||||
<a href="{{ path('user_new') }}" class="btn btn-primary">Ajouter un utilisateur</a>
|
||||
</div>
|
||||
|
||||
|
||||
<table class="table align-middle shadow">
|
||||
<thead class="table-light shadow-sm">
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Surname</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Visualiser</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ user.surname }}</td>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<a href="{{ path('user_show', {'id': user.id}) }}" class="p-3 align-middle">
|
||||
<i class="icon-grid menu-icon color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if users|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">Aucun utilisateur trouvé.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Ajouter un utilisateur{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-100 h-100 p-5 m-auto">
|
||||
<h1>Ajouter un utilisateur</h1>
|
||||
<form method="post" action="{{ path('user_new') }}" enctype="multipart/form-data">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
{{ form_end(form) }}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="col-md-10 m-auto p-5">
|
||||
<div class="col d-flex justify-content-between align-items-center ">
|
||||
<h1 class="mb-4">Gestion Utilisateur</h1>
|
||||
<a href="{{ path('user_deactivate', {'id': user.id}) }}" class="btn btn-danger">Désactiver</a>
|
||||
</div>
|
||||
{% include 'elements/userInformation.html.twig' %}
|
||||
|
||||
<h1 class="mt-5 mb-4">Organisations</h1>
|
||||
<div class="row">
|
||||
{% if userOrganizations is empty %}
|
||||
<div class="col-md-10 m-auto p-auto">
|
||||
<h3>Aucune organisation associée à cet utilisateur.</h3>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for organization in userOrganizations %}
|
||||
{% include 'elements/userOrganizationInformation.html.twig'
|
||||
with {'organization': organization.organization, 'roles': organization.roles, 'apps': organization.apps} %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Loading…
Reference in New Issue