diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..284f7fd --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +help: ## Show this help message + @echo 'Usage:' + @echo ' make ' + @echo '' + @echo 'Targets:' + @awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_\/-]+:.*?##/ { printf " %-20s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +dev/start-server: ## Start the Symfony server + symfony server:start + +dev/install-deps: ## Install the dependencies + composer install + +db/generate-migration: ## Generate a new migration + php bin/console make:migration + +db/create: ## Create the database + php bin/console doctrine:database:create + +db/migrate: ## Run the migrations + php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing + +test: ## Run the tests + php bin/phpunit + +lint: ## Run the linter + vendor/bin/php-cs-fixer fix + +imports/import-themes: ## Import the themes + php bin/console ExtractService extractthemes + diff --git a/README.md b/README.md index 7eb0da5..b582f62 100644 --- a/README.md +++ b/README.md @@ -19,18 +19,18 @@ This project is a web-based visualization tool for exploring and analyzing open 3. Install Composer (PHP dependency manager): - Follow installation steps at: https://getcomposer.org/download/ - - Run `composer install` to install dependencies + - Run `make dev/install-deps` to install dependencies 4. Run migrations: ``` - composer db:create - composer db:migrate + make db/create + make db/migrate ``` 5. Set up local development server: - Install Symfony CLI from: https://symfony.com/download - Build database schema: `symfony console doctrine:schema:update --force` - - Start the server: `symfony server:start` + - Start the server: `make dev/start-server` 6. Access the application: - Open your browser and navigate to: http://localhost:8000/ @@ -38,4 +38,4 @@ This project is a web-based visualization tool for exploring and analyzing open ## Running tests - Build database schema: `symfony console doctrine:schema:update --force --env=test` -- Run tests: `php bin/phpunit` +- Run tests: `make test` diff --git a/composer.json b/composer.json index 86eb592..903105b 100644 --- a/composer.json +++ b/composer.json @@ -64,11 +64,7 @@ ], "post-update-cmd": [ "@auto-scripts" - ], - "fix-style": "php-cs-fixer fix", - "db:migrate": "php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing", - "db:create": "php bin/console doctrine:database:create", - "migration:create": "php bin/console make:migration" + ] }, "conflict": { "symfony/symfony": "*" diff --git a/src/Controller/Api/ThemesController.php b/src/Controller/Api/ThemesController.php index 996ee36..1992fab 100644 --- a/src/Controller/Api/ThemesController.php +++ b/src/Controller/Api/ThemesController.php @@ -2,6 +2,7 @@ namespace App\Controller\Api; +use App\Repository\ThemeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; @@ -9,31 +10,16 @@ #[Route('/api')] final class ThemesController extends AbstractController { + private $themeRepository; + + public function __construct(ThemeRepository $themeRepository) + { + $this->themeRepository = $themeRepository; + } + #[Route('/themes', name: 'app_themes', methods: ['GET'])] public function index(): JsonResponse { - return $this->json([ - 'themes' => [ - [ - 'code' => 'environment', - 'id' => 1234, - 'parentId' => null, - 'children' => [ - [ - 'code' => 'some_sub_theme', - 'id' => 1024, - 'parentId' => 1234, - 'children' => [ - [ - 'code' => 'yet_another_sub_theme', - 'id' => 2048, - 'parentId' => 1024, - ], - ], - ], - ], - ], - ], - ]); + return $this->json(['themes' => $this->themeRepository->findAllHierarchical()]); } } diff --git a/src/Repository/ThemeRepository.php b/src/Repository/ThemeRepository.php index 8d8d09c..9f2bed5 100644 --- a/src/Repository/ThemeRepository.php +++ b/src/Repository/ThemeRepository.php @@ -8,10 +8,21 @@ class ThemeRepository extends ServiceEntityRepository { - private $entityManager; - public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Theme::class); } + + public function findAllHierarchical(): array + { + $themes = $this->findAll(); + $themesByParentId = []; + array_map(function (Theme $theme) use (&$themesByParentId) { + $children = $themesByParentId[$theme->getParentId()] ?? []; + $children[] = ['id' => $theme->getId(), 'name' => $theme->getName(), 'parentId' => $theme->getParentId(), 'externalId' => $theme->getExternalId()]; + $themesByParentId[$theme->getParentId() ?? 'base'] = $children; + }, $themes); + + return $themesByParentId; + } } diff --git a/tests/Api/ThemesTest.php b/tests/Api/ThemesTest.php index db7ad09..0d4b82c 100644 --- a/tests/Api/ThemesTest.php +++ b/tests/Api/ThemesTest.php @@ -2,21 +2,51 @@ namespace App\Tests; +use App\Entity\Theme; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class ThemesTest extends WebTestCase { - // Ideally this should be replaced with a more comprehensive test - // once we have our data model in place + private $client; + private $themeRepository; + private $entityManager; + + protected function setUp(): void + { + parent::setUp(); + $this->client = static::createClient(); + + $container = static::getContainer(); + $this->entityManager = $container->get('doctrine.orm.entity_manager'); + $this->themeRepository = $this->entityManager->getRepository(Theme::class); + + $parent = new Theme(); + $parent->setName('Environment'); + $parent->setIsSection(true); + $parent->setParentId(null); + $parent->setExternalId('external_id1'); + $this->entityManager->persist($parent); + $this->entityManager->flush(); + } + + protected function tearDown(): void + { + $themes = $this->themeRepository->findAll(); + foreach ($themes as $theme) { + $this->entityManager->remove($theme); + } + $this->entityManager->flush(); + parent::tearDown(); + } + public function testApiResponse(): void { - $client = static::createClient(); - $client->request('GET', '/api/themes'); + $this->client->request('GET', '/api/themes'); $this->assertResponseIsSuccessful(); - $content = $client->getResponse()->getContent(); + $content = $this->client->getResponse()->getContent(); $results = json_decode($content, true); - $this->assertEquals('environment', $results['themes'][0]['code']); + $this->assertEquals('Environment', $results['themes']['base'][0]['name']); } } diff --git a/tests/Themes/ThemeRepositoryTest.php b/tests/Themes/ThemeRepositoryTest.php index 9b84cde..89dd892 100644 --- a/tests/Themes/ThemeRepositoryTest.php +++ b/tests/Themes/ThemeRepositoryTest.php @@ -53,4 +53,42 @@ public function testAddChildTheme(): void $this->entityManager->flush(); $this->assertNotNull($child->getId()); } + + public function testFindAllHierarchical(): void + { + $themes = $this->themeRepository->findAllHierarchical(); + $this->assertEquals(0, count($themes)); + + $parent = new Theme(); + $parent->setName('Environment'); + $parent->setIsSection(true); + $parent->setParentId(null); + $parent->setExternalId('external_id1'); + $this->entityManager->persist($parent); + $this->entityManager->flush(); + + $child = new Theme(); + $child->setName('Climate Change'); + $child->setIsSection(true); + $child->setParentId($parent->getId()); + $child->setExternalId('external_id2'); + $this->entityManager->persist($child); + $this->entityManager->flush(); + + $grandchild = new Theme(); + $grandchild->setName('Sea Level Rise'); + $grandchild->setIsSection(true); + $grandchild->setParentId($child->getId()); + $grandchild->setExternalId('external_id3'); + $this->entityManager->persist($grandchild); + $this->entityManager->flush(); + + $themes = $this->themeRepository->findAllHierarchical(); + $this->assertEquals(3, count($themes)); + $this->assertEquals('Environment', $themes['base'][0]['name']); + $topParentId = $themes['base'][0]['id']; + $this->assertEquals('Climate Change', $themes[$topParentId][0]['name']); + $midParentId = $themes[$topParentId][0]['id']; + $this->assertEquals('Sea Level Rise', $themes[$midParentId][0]['name']); + } }