diff --git a/symfony/migrations/Version20241105102005.php b/symfony/migrations/Version20241113164118.php similarity index 95% rename from symfony/migrations/Version20241105102005.php rename to symfony/migrations/Version20241113164118.php index 8048c74a..536f0b57 100644 --- a/symfony/migrations/Version20241105102005.php +++ b/symfony/migrations/Version20241113164118.php @@ -10,7 +10,7 @@ /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20241105102005 extends AbstractMigration +final class Version20241113164118 extends AbstractMigration { public function getDescription(): string { @@ -26,7 +26,7 @@ public function up(Schema $schema): void $this->addSql('CREATE SEQUENCE project_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE thematic_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE "user_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE actor (id UUID NOT NULL, logo_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, name VARCHAR(255) NOT NULL, acronym VARCHAR(255) NOT NULL, is_validated BOOLEAN NOT NULL, category VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, office_name VARCHAR(255) DEFAULT NULL, office_address VARCHAR(255) DEFAULT NULL, office_location geometry(POINT, 0) DEFAULT NULL, contact_name VARCHAR(255) DEFAULT NULL, contact_position VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, phone VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, external_images TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, slug VARCHAR(128) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE actor (id UUID NOT NULL, logo_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, name VARCHAR(255) NOT NULL, acronym VARCHAR(255) NOT NULL, category VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, office_name VARCHAR(255) DEFAULT NULL, office_address VARCHAR(255) DEFAULT NULL, office_location geometry(POINT, 0) DEFAULT NULL, contact_name VARCHAR(255) DEFAULT NULL, contact_position VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, phone VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, external_images TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, slug VARCHAR(128) DEFAULT NULL, is_validated BOOLEAN NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_447556F9989D9B62 ON actor (slug)'); $this->addSql('CREATE INDEX IDX_447556F9F98F144A ON actor (logo_id)'); $this->addSql('CREATE INDEX IDX_447556F9DE12AB56 ON actor (created_by)'); @@ -52,7 +52,7 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE actor_expertise (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE administrative_scope (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE media_object (id INT NOT NULL, file_path VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE project (id INT NOT NULL, actor_id UUID NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, name VARCHAR(255) NOT NULL, location VARCHAR(255) NOT NULL, coords geometry(POINT, 0) NOT NULL, status VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, images JSON DEFAULT NULL, partners JSON DEFAULT NULL, intervention_zone VARCHAR(255) NOT NULL, project_manager_name VARCHAR(255) DEFAULT NULL, project_manager_position VARCHAR(255) DEFAULT NULL, project_manager_email VARCHAR(255) DEFAULT NULL, project_manager_tel VARCHAR(255) DEFAULT NULL, project_manager_photo VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, logo VARCHAR(255) DEFAULT NULL, deliverables TEXT DEFAULT NULL, calendar TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, slug VARCHAR(128) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE project (id INT NOT NULL, actor_id UUID NOT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, name VARCHAR(255) NOT NULL, location VARCHAR(255) NOT NULL, coords geometry(POINT, 0) NOT NULL, status VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, images JSON DEFAULT NULL, partners JSON DEFAULT NULL, intervention_zone VARCHAR(255) NOT NULL, project_manager_name VARCHAR(255) DEFAULT NULL, project_manager_position VARCHAR(255) DEFAULT NULL, project_manager_email VARCHAR(255) DEFAULT NULL, project_manager_tel VARCHAR(255) DEFAULT NULL, project_manager_photo VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, logo VARCHAR(255) DEFAULT NULL, deliverables TEXT DEFAULT NULL, calendar TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, slug VARCHAR(128) DEFAULT NULL, is_validated BOOLEAN NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE UNIQUE INDEX UNIQ_2FB3D0EE989D9B62 ON project (slug)'); $this->addSql('CREATE INDEX IDX_2FB3D0EE10DAF24A ON project (actor_id)'); $this->addSql('CREATE INDEX IDX_2FB3D0EEDE12AB56 ON project (created_by)'); diff --git a/symfony/src/DataFixtures/Processor/ActorProjectProcessor.php b/symfony/src/DataFixtures/Processor/ActorProjectProcessor.php index 57efd0a9..fbc405e7 100644 --- a/symfony/src/DataFixtures/Processor/ActorProjectProcessor.php +++ b/symfony/src/DataFixtures/Processor/ActorProjectProcessor.php @@ -11,7 +11,7 @@ public function preProcess(string $fixtureId, $object): void { // For testing purpose static $callCount = 0; - if ($object instanceof Actor) { + if ($object instanceof Actor || $object instanceof Project) { $callCount++; $isValidated = $callCount % 4 !== 0; $object->setIsValidated($isValidated); diff --git a/symfony/src/Entity/Actor.php b/symfony/src/Entity/Actor.php index 1ea1246b..698e2ed9 100644 --- a/symfony/src/Entity/Actor.php +++ b/symfony/src/Entity/Actor.php @@ -23,10 +23,11 @@ use App\Entity\Trait\SluggableEntity; use ApiPlatform\Metadata\GetCollection; use App\Entity\Trait\TimestampableEntity; +use App\Entity\Trait\ToValidateEntity; use Doctrine\Common\Collections\Collection; use Jsor\Doctrine\PostGIS\Types\PostGISType; use App\Services\State\Provider\ActorProvider; -use App\Services\State\Processor\ActorProcessor; +use App\Services\State\Processor\ValidateEntityProcessor; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -42,15 +43,15 @@ ), new Get(), new Post( - processor: ActorProcessor::class, + processor: ValidateEntityProcessor::class, security: "is_granted('".UserRoles::ROLE_EDITOR_ACTORS."')" ), new Patch( - processor: ActorProcessor::class, + processor: ValidateEntityProcessor::class, security: 'is_granted("'.ActorVoter::EDIT.'", object)' ), new Put( - processor: ActorProcessor::class, + processor: ValidateEntityProcessor::class, security: 'is_granted("'.ActorVoter::EDIT.'", object)' ), new Delete( @@ -69,6 +70,7 @@ class Actor use TimestampableEntity; use SluggableEntity; use BlameableEntity; + use ToValidateEntity; #[ORM\Id] #[ORM\Column(type: 'uuid', unique: true)] @@ -85,10 +87,6 @@ class Actor #[Groups([self::ACTOR_READ_COLLECTION, self::ACTOR_READ_ITEM, self::ACTOR_WRITE, Project::PROJECT_READ_ALL, Project::PROJECT_READ])] private ?string $acronym = null; - #[ORM\Column] - #[Groups([self::ACTOR_READ_ITEM, self::ACTOR_READ_COLLECTION])] - private ?bool $isValidated = false; - #[ORM\Column(enumType: ActorCategory::class)] #[Groups([self::ACTOR_READ_COLLECTION, self::ACTOR_READ_ITEM, self::ACTOR_WRITE, Project::PROJECT_READ])] private ?ActorCategory $category = null; @@ -216,18 +214,6 @@ public function setAcronym(string $acronym): static return $this; } - public function getIsValidated(): ?bool - { - return $this->isValidated; - } - - public function setIsValidated(bool $isValidated): static - { - $this->isValidated = $isValidated; - - return $this; - } - public function getCategory(): ?ActorCategory { return $this->category; diff --git a/symfony/src/Entity/Project.php b/symfony/src/Entity/Project.php index 7774d6ce..3c458720 100644 --- a/symfony/src/Entity/Project.php +++ b/symfony/src/Entity/Project.php @@ -2,25 +2,27 @@ namespace App\Entity; -use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface; -use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\GetCollection; -use App\Enum\AdministrativeScope; use App\Enum\Status; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use App\Enum\AdministrativeScope; +use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\QueryParameter; -use App\Controller\Project\SimilarProjectsAction; -use App\Repository\ProjectRepository; +use App\Entity\Trait\BlameableEntity; use App\Entity\Trait\SluggableEntity; +use App\Repository\ProjectRepository; +use App\Entity\Trait\ToValidateEntity; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\QueryParameter; use App\Entity\Trait\TimestampableEntity; -use App\Entity\Trait\BlameableEntity; use Doctrine\Common\Collections\Collection; use Jsor\Doctrine\PostGIS\Types\PostGISType; use Doctrine\Common\Collections\ArrayCollection; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use App\Controller\Project\SimilarProjectsAction; use Symfony\Component\Serializer\Attribute\Groups; +use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface; +use App\Services\State\Provider\ProjectProvider; #[ORM\Entity(repositoryClass: ProjectRepository::class)] #[ApiFilter(filterClass: SearchFilter::class, properties: ['slug' => SearchFilterInterface::STRATEGY_EXACT])] @@ -28,6 +30,7 @@ paginationEnabled: false, operations: [ new GetCollection( + provider: ProjectProvider::class, uriTemplate: '/projects/all', normalizationContext: ['groups' => [self::PROJECT_READ_ALL]] ), @@ -49,6 +52,7 @@ class Project use TimestampableEntity; use BlameableEntity; use SluggableEntity; + use ToValidateEntity; public const PROJECT_READ = 'project:read'; public const PROJECT_READ_ALL = 'project:read:all'; diff --git a/symfony/src/Entity/Trait/ToValidateEntity.php b/symfony/src/Entity/Trait/ToValidateEntity.php new file mode 100644 index 00000000..1676c6c8 --- /dev/null +++ b/symfony/src/Entity/Trait/ToValidateEntity.php @@ -0,0 +1,27 @@ +isValidated; + } + + public function setIsValidated(bool $isValidated): static + { + $this->isValidated = $isValidated; + + return $this; + } +} diff --git a/symfony/src/Services/State/Processor/ActorProcessor.php b/symfony/src/Services/State/Processor/ValidateEntityProcessor.php similarity index 94% rename from symfony/src/Services/State/Processor/ActorProcessor.php rename to symfony/src/Services/State/Processor/ValidateEntityProcessor.php index 5159538e..c6da665a 100644 --- a/symfony/src/Services/State/Processor/ActorProcessor.php +++ b/symfony/src/Services/State/Processor/ValidateEntityProcessor.php @@ -10,7 +10,7 @@ use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\RequestStack; -class ActorProcessor implements ProcessorInterface +class ValidateEntityProcessor implements ProcessorInterface { diff --git a/symfony/src/Services/State/Provider/ProjectProvider.php b/symfony/src/Services/State/Provider/ProjectProvider.php new file mode 100644 index 00000000..4944d136 --- /dev/null +++ b/symfony/src/Services/State/Provider/ProjectProvider.php @@ -0,0 +1,28 @@ +security->isGranted(UserRoles::ROLE_ADMIN)) { + return $this->projectRepository->findAll(); + } + + return $this->projectRepository->findBy(['isValidated' => true]); + } +} \ No newline at end of file diff --git a/vue/src/stores/actorsStore.ts b/vue/src/stores/actorsStore.ts index b5f5f7b5..a18108c6 100644 --- a/vue/src/stores/actorsStore.ts +++ b/vue/src/stores/actorsStore.ts @@ -20,10 +20,12 @@ export const useActorsStore = defineStore(StoresList.ACTORS, () => { const actorsThematics: Thematic[] = reactive([]) const actorsAdministrativesScopes: AdministrativeScope[] = reactive([]) - async function getActors(): Promise { + async function getActors(forceGet = false): Promise { + if (actors.length === 0 || forceGet) { actors.splice(0, actors.length) actors.push(...await ActorsService.getActors()) dataLoaded.value = true + } } async function getActorsSelectListContent(): Promise { diff --git a/vue/src/stores/applicationStore.ts b/vue/src/stores/applicationStore.ts index 63402cc5..5a3ebacf 100644 --- a/vue/src/stores/applicationStore.ts +++ b/vue/src/stores/applicationStore.ts @@ -9,6 +9,7 @@ import { useProjectStore } from '@/stores/projectStore' import { DialogKey } from '@/models/enums/app/DialogKey' import type { Ref } from 'vue' import { ContentPagesList } from '@/models/enums/app/ContentPagesList' +import { useActorsStore } from './actorsStore' export const useApplicationStore = defineStore(StoresList.APPLICATION, () => { const { mobile } = useDisplay() @@ -35,5 +36,12 @@ export const useApplicationStore = defineStore(StoresList.APPLICATION, () => { return route.name && typeof route.name === 'string' && lightUiRoutes.includes(route.name) }) - return { mobile, showSnackBar, snackBarMessage, activeTab, activeDialog, breadcrumbs, is100vh, isLightHeader, triggerZoomReset, showEditContentDialog, currentContentPage } + const refreshAppData = () => { + const actorsStore = useActorsStore() + actorsStore.getActors(true) + const projectStore = useProjectStore() + projectStore.getAll(true) + } + + return { mobile, showSnackBar, snackBarMessage, activeTab, activeDialog, breadcrumbs, is100vh, isLightHeader, triggerZoomReset, showEditContentDialog, currentContentPage, refreshAppData } }) diff --git a/vue/src/stores/projectStore.ts b/vue/src/stores/projectStore.ts index 2baade11..ce9bf7e7 100644 --- a/vue/src/stores/projectStore.ts +++ b/vue/src/stores/projectStore.ts @@ -39,8 +39,8 @@ export const useProjectStore = defineStore(StoresList.PROJECTS, () => { financialActors: [], }) - async function getAll(): Promise { - if (projects.value.length === 0) { + async function getAll(forceGet = false): Promise { + if (projects.value.length === 0 || forceGet) { projects.value = await ProjectService.getAll() } } diff --git a/vue/src/stores/userStore.ts b/vue/src/stores/userStore.ts index 9bcb5ee0..a0b0a4e0 100644 --- a/vue/src/stores/userStore.ts +++ b/vue/src/stores/userStore.ts @@ -12,6 +12,7 @@ import { UserRoles } from '@/models/enums/auth/UserRoles' import { ImageLoader } from '@/services/files/ImageLoader' import type { MediaObject } from '@/models/interfaces/MediaObject' import { UserService } from '@/services/userAndAuth/UserService' +import { useApplicationStore } from './applicationStore' export const useUserStore = defineStore(StoresList.USER, () => { @@ -44,6 +45,8 @@ export const useUserStore = defineStore(StoresList.USER, () => { const setCurrentUser = async () => { currentUser.value = (await AuthenticationService.getAuthenticatedUser()).data + const appStore = useApplicationStore() + appStore.refreshAppData() } const signUp = async (values: SignUpValues) => { @@ -65,6 +68,8 @@ export const useUserStore = defineStore(StoresList.USER, () => { currentUser.value = null JwtCookie.clearCookies() router.push({ name: 'home' }) + const appStore = useApplicationStore() + appStore.refreshAppData() } const checkAuthenticated = async () => { diff --git a/vue/src/views/actors/ActorsView.vue b/vue/src/views/actors/ActorsView.vue index 36e03115..0db1238a 100644 --- a/vue/src/views/actors/ActorsView.vue +++ b/vue/src/views/actors/ActorsView.vue @@ -72,7 +72,7 @@
- {{ `${actorsStore.actors.length.toString()} ${actorsStore.actors.length > 1 ? $t('actors.actors') : $t('actors.actor')}` }} + {{ `${totalActors.length.toString()} ${$t('admin.member', totalActors.length)}`}} {{ $t('actors.resetFilters') }}
{{ filteredActors.length }}
@@ -138,8 +138,9 @@ const selectedAdminScope: Ref = ref(null); const categoryItems = Object.values(ActorsCategories) const selectedCategory: Ref = ref(null); +const totalActors = computed(() => actorsStore.actors.filter(x => x.isValidated)) const filteredActors = computed(() => { - let filteredActors = [...actorsStore.actors] + let filteredActors = [...totalActors.value] if (searchQuery.value) { filteredActors = filteredActors.filter((actor: Actor) => {