Skip to content
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ All notable changes to this project will be documented in this file.
* Removed propTypes.
* Upgraded redux-toolkit and how api slices are generated.
* Fixed redux-toolkit cache handling.
* Add Taskfile
* Added Taskfile
* Added update command.
* Added (Client) online-check to public.
* Updated developer documentation.

Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,12 @@ To get started with the development setup, run the following task command:

```shell
task site-install

# or if you want to load fixtures as well
task site-install-with-fixtures
```

If you want to load fixtures, use the command (use the option `--yes` for auto-confirming).
If you want to load fixtures manually, use the command (`--yes` for auto-confirming):

```shell
task fixtures:load --yes
Expand All @@ -92,7 +95,7 @@ The fixtures have an admin user: <[email protected]> with the password: "apasswo

The fixtures have an editor user: <[email protected]> with the password: "apassword".

The fixtures have the image-text template, and two screen layouts: full screen and "two boxes".
The fixtures have the image-text template, and two screen layouts: "full screen" and "two boxes".

## Production setup

Expand All @@ -107,6 +110,12 @@ APP_SECRET=<GENERATE A NEW SECRET>

TODO: Add further production instructions: Build steps, release.json, etc.

Use the `app:update` command to migrate and update templates to latest version:

```shell
docker compose exec phpfpm bin/console app:update --no-interaction
```

## Coding standards

Before a PR can be merged it has to pass the GitHub Actions checks. See `.github/workflows` for workflows that should
Expand Down
5 changes: 5 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,8 @@ tasks:
desc: "Generate the RTK Query api slices (in assets/shared/redux/)."
cmds:
- task compose -- exec node npx @rtk-query/codegen-openapi /app/assets/shared/redux/openapi-config.js

app:update:
desc: "Migrate to latest database schema and update installed templates"
cmds:
- task compose -- exec phpfpm bin/console app:update --no-interaction
56 changes: 56 additions & 0 deletions src/Command/StatusCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'app:status',
description: 'Returns current status of the application',
)]
class StatusCommand extends Command
{
final protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$application = $this->getApplication();

if (null === $application) {
$io->error('Application not initialized.');

return Command::FAILURE;
}

$io->title('Migrations status');

// Check status for migrations.
$command = new ArrayInput([
'command' => 'doctrine:migrations:up-to-date',
]);
$application->doRun($command, $output);

$io->writeln('');
$io->writeln('');
$io->writeln('');
$io->title('Templates status');

// List status for templates.
$command = new ArrayInput([
'command' => 'app:templates:list',
'--status' => true,
]);
$application->doRun($command, $output);

$io->info('Run app:update to update migrations and templates.');

return Command::SUCCESS;
}
}
30 changes: 24 additions & 6 deletions src/Command/TemplatesListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

Expand All @@ -24,10 +25,17 @@ public function __construct(
parent::__construct();
}

protected function configure(): void
{
$this->addOption('status', 's', InputOption::VALUE_NONE, 'Get status of installed templates.');
}

final protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$status = $input->getOption('status');

try {
$templates = $this->templateService->getCoreTemplates();

Expand All @@ -39,12 +47,22 @@ final protected function execute(InputInterface $input, OutputInterface $output)

$customTemplates = $this->templateService->getCustomTemplates();

$io->table(['ID', 'Title', 'Status', 'Type'], array_map(fn (TemplateData $templateData) => [
$templateData->id,
$templateData->title,
$templateData->installed ? 'Installed' : 'Not Installed',
$templateData->type,
], array_merge($templates, $customTemplates)));
$allTemplates = array_merge($templates, $customTemplates);

if ($status) {
$numberOfTemplates = count($allTemplates);
$numberOfInstallledTemplates = count(array_filter($allTemplates, fn ($entry): bool => $entry->installed));
$text = $numberOfInstallledTemplates.' / '.$numberOfTemplates.' templates installed.';

$io->success($text);
} else {
$io->table(['ID', 'Title', 'Status', 'Type'], array_map(fn (TemplateData $templateData) => [
$templateData->id,
$templateData->title,
$templateData->installed ? 'Installed' : 'Not Installed',
$templateData->type,
], $allTemplates));
}

return Command::SUCCESS;
} catch (\Exception $e) {
Expand Down
40 changes: 40 additions & 0 deletions src/Command/TemplatesUpdateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace App\Command;

use App\Service\TemplateService;
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;

#[AsCommand(
name: 'app:templates:update',
description: 'Update installed templates',
)]
class TemplatesUpdateCommand extends Command
{
public function __construct(
private readonly TemplateService $templateService,
) {
parent::__construct();
}

final protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$templates = $this->templateService->getAllTemplates();

foreach ($templates as $templateToUpdate) {
$this->templateService->updateTemplate($templateToUpdate);
}

$io->success('Updated all installed templates');

return Command::SUCCESS;
}
}
78 changes: 74 additions & 4 deletions src/Command/UpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,79 @@

namespace App\Command;

class UpdateCommand
use App\Service\TemplateService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'app:update',
description: 'Run required updates.',
)]
class UpdateCommand extends Command
{
// TODO: Test that migrations have been run.
// TODO: Run test of status for templates. No templates = clean install. Install all?
// TODO: Update existing templates.
private TemplateService $templateService;

public function __construct(TemplateService $templateService, ?string $name = null)
{
parent::__construct($name);
$this->templateService = $templateService;
}

final protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$isInteractive = $input->isInteractive();

$application = $this->getApplication();

if (null === $application) {
$io->error('Application not initialized.');

return Command::FAILURE;
}

$command = new ArrayInput([
'command' => 'doctrine:migrations:migrate',
]);
$command->setInteractive($isInteractive);
$result = $application->doRun($command, $output);

if (0 !== $result) {
$io->info('Update aborted. Migrations need to run for the system to work. Run doctrine:migrations:migrate or rerun app:update to migrate.');

return Command::FAILURE;
}

$allTemplates = $this->templateService->getAllTemplates();
$installedTemplates = array_filter($allTemplates, fn ($entry): bool => $entry->installed);

// If no installed templates, we assume that this is a new installation and offer to install all templates.
if ($isInteractive && 0 === count($installedTemplates)) {
$question = new Question('No templates are installed. Install all '.count($allTemplates).'?', 'yes');
$question->setAutocompleterValues(['yes', 'no']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$installAll = $io->askQuestion($question);

if ('yes' === $installAll) {
$io->info('Installing all templates...');
$command = new ArrayInput([
'command' => 'app:templates:install',
'--all' => true,
]);
$application->doRun($command, $output);
}
} else {
$io->info('Updating existing template...');
$command = new ArrayInput([
'command' => 'app:templates:update',
]);
$application->doRun($command, $output);
}

return Command::SUCCESS;
}
}
14 changes: 14 additions & 0 deletions src/Service/TemplateService.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ public function installTemplate(TemplateData $templateData, bool $update = false
$this->entityManager->flush();
}

public function updateTemplate(TemplateData $templateData): void
{
$template = $templateData->templateEntity;

// Ignore templates that do not exist in the database.
if (null === $template) {
return;
}

$template->setTitle($templateData->title);

$this->entityManager->flush();
}

public function getAllTemplates(): array
{
return array_merge($this->getCoreTemplates(), $this->getCustomTemplates());
Expand Down