Skip to content

Commit 2965c4b

Browse files
authored
Merge pull request #272 from os2display/feature/update-command
Update command
2 parents fb32d73 + f225a7d commit 2965c4b

File tree

8 files changed

+225
-13
lines changed

8 files changed

+225
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ All notable changes to this project will be documented in this file.
1414
* Removed propTypes.
1515
* Upgraded redux-toolkit and how api slices are generated.
1616
* Fixed redux-toolkit cache handling.
17-
* Add Taskfile
17+
* Added Taskfile
18+
* Added update command.
1819
* Added (Client) online-check to public.
1920
* Updated developer documentation.
2021

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@ To get started with the development setup, run the following task command:
8080

8181
```shell
8282
task site-install
83+
84+
# or if you want to load fixtures as well
85+
task site-install-with-fixtures
8386
```
8487

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

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

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

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

97100
## Production setup
98101

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

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

113+
Use the `app:update` command to migrate and update templates to latest version:
114+
115+
```shell
116+
docker compose exec phpfpm bin/console app:update --no-interaction
117+
```
118+
110119
## Coding standards
111120

112121
Before a PR can be merged it has to pass the GitHub Actions checks. See `.github/workflows` for workflows that should

Taskfile.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,8 @@ tasks:
216216
desc: "Generate the RTK Query api slices (in assets/shared/redux/)."
217217
cmds:
218218
- task compose -- exec node npx @rtk-query/codegen-openapi /app/assets/shared/redux/openapi-config.js
219+
220+
app:update:
221+
desc: "Migrate to latest database schema and update installed templates"
222+
cmds:
223+
- task compose -- exec phpfpm bin/console app:update --no-interaction

src/Command/StatusCommand.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Command;
6+
7+
use Symfony\Component\Console\Attribute\AsCommand;
8+
use Symfony\Component\Console\Command\Command;
9+
use Symfony\Component\Console\Input\ArrayInput;
10+
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Output\OutputInterface;
12+
use Symfony\Component\Console\Style\SymfonyStyle;
13+
14+
#[AsCommand(
15+
name: 'app:status',
16+
description: 'Returns current status of the application',
17+
)]
18+
class StatusCommand extends Command
19+
{
20+
final protected function execute(InputInterface $input, OutputInterface $output): int
21+
{
22+
$io = new SymfonyStyle($input, $output);
23+
24+
$application = $this->getApplication();
25+
26+
if (null === $application) {
27+
$io->error('Application not initialized.');
28+
29+
return Command::FAILURE;
30+
}
31+
32+
$io->title('Migrations status');
33+
34+
// Check status for migrations.
35+
$command = new ArrayInput([
36+
'command' => 'doctrine:migrations:up-to-date',
37+
]);
38+
$application->doRun($command, $output);
39+
40+
$io->writeln('');
41+
$io->writeln('');
42+
$io->writeln('');
43+
$io->title('Templates status');
44+
45+
// List status for templates.
46+
$command = new ArrayInput([
47+
'command' => 'app:templates:list',
48+
'--status' => true,
49+
]);
50+
$application->doRun($command, $output);
51+
52+
$io->info('Run app:update to update migrations and templates.');
53+
54+
return Command::SUCCESS;
55+
}
56+
}

src/Command/TemplatesListCommand.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Console\Attribute\AsCommand;
1010
use Symfony\Component\Console\Command\Command;
1111
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
1213
use Symfony\Component\Console\Output\OutputInterface;
1314
use Symfony\Component\Console\Style\SymfonyStyle;
1415

@@ -24,10 +25,17 @@ public function __construct(
2425
parent::__construct();
2526
}
2627

28+
protected function configure(): void
29+
{
30+
$this->addOption('status', 's', InputOption::VALUE_NONE, 'Get status of installed templates.');
31+
}
32+
2733
final protected function execute(InputInterface $input, OutputInterface $output): int
2834
{
2935
$io = new SymfonyStyle($input, $output);
3036

37+
$status = $input->getOption('status');
38+
3139
try {
3240
$templates = $this->templateService->getCoreTemplates();
3341

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

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

42-
$io->table(['ID', 'Title', 'Status', 'Type'], array_map(fn (TemplateData $templateData) => [
43-
$templateData->id,
44-
$templateData->title,
45-
$templateData->installed ? 'Installed' : 'Not Installed',
46-
$templateData->type,
47-
], array_merge($templates, $customTemplates)));
50+
$allTemplates = array_merge($templates, $customTemplates);
51+
52+
if ($status) {
53+
$numberOfTemplates = count($allTemplates);
54+
$numberOfInstallledTemplates = count(array_filter($allTemplates, fn ($entry): bool => $entry->installed));
55+
$text = $numberOfInstallledTemplates.' / '.$numberOfTemplates.' templates installed.';
56+
57+
$io->success($text);
58+
} else {
59+
$io->table(['ID', 'Title', 'Status', 'Type'], array_map(fn (TemplateData $templateData) => [
60+
$templateData->id,
61+
$templateData->title,
62+
$templateData->installed ? 'Installed' : 'Not Installed',
63+
$templateData->type,
64+
], $allTemplates));
65+
}
4866

4967
return Command::SUCCESS;
5068
} catch (\Exception $e) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Command;
6+
7+
use App\Service\TemplateService;
8+
use Symfony\Component\Console\Attribute\AsCommand;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Output\OutputInterface;
12+
use Symfony\Component\Console\Style\SymfonyStyle;
13+
14+
#[AsCommand(
15+
name: 'app:templates:update',
16+
description: 'Update installed templates',
17+
)]
18+
class TemplatesUpdateCommand extends Command
19+
{
20+
public function __construct(
21+
private readonly TemplateService $templateService,
22+
) {
23+
parent::__construct();
24+
}
25+
26+
final protected function execute(InputInterface $input, OutputInterface $output): int
27+
{
28+
$io = new SymfonyStyle($input, $output);
29+
30+
$templates = $this->templateService->getAllTemplates();
31+
32+
foreach ($templates as $templateToUpdate) {
33+
$this->templateService->updateTemplate($templateToUpdate);
34+
}
35+
36+
$io->success('Updated all installed templates');
37+
38+
return Command::SUCCESS;
39+
}
40+
}

src/Command/UpdateCommand.php

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,78 @@
44

55
namespace App\Command;
66

7-
class UpdateCommand
7+
use App\Service\TemplateService;
8+
use Symfony\Component\Console\Attribute\AsCommand;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Input\ArrayInput;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Output\OutputInterface;
13+
use Symfony\Component\Console\Question\ConfirmationQuestion;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
16+
#[AsCommand(
17+
name: 'app:update',
18+
description: 'Run required updates.',
19+
)]
20+
class UpdateCommand extends Command
821
{
9-
// TODO: Test that migrations have been run.
10-
// TODO: Run test of status for templates. No templates = clean install. Install all?
11-
// TODO: Update existing templates.
22+
private TemplateService $templateService;
23+
24+
public function __construct(TemplateService $templateService, ?string $name = null)
25+
{
26+
parent::__construct($name);
27+
$this->templateService = $templateService;
28+
}
29+
30+
final protected function execute(InputInterface $input, OutputInterface $output): int
31+
{
32+
$io = new SymfonyStyle($input, $output);
33+
$isInteractive = $input->isInteractive();
34+
35+
$application = $this->getApplication();
36+
37+
if (null === $application) {
38+
$io->error('Application not initialized.');
39+
40+
return Command::FAILURE;
41+
}
42+
43+
$command = new ArrayInput([
44+
'command' => 'doctrine:migrations:migrate',
45+
]);
46+
$command->setInteractive($isInteractive);
47+
$result = $application->doRun($command, $output);
48+
49+
if (0 !== $result) {
50+
$io->info('Update aborted. Migrations need to run for the system to work. Run doctrine:migrations:migrate or rerun app:update to migrate.');
51+
52+
return Command::FAILURE;
53+
}
54+
55+
$allTemplates = $this->templateService->getAllTemplates();
56+
$installedTemplates = array_filter($allTemplates, fn ($entry): bool => $entry->installed);
57+
58+
// If no installed templates, we assume that this is a new installation and offer to install all templates.
59+
if ($isInteractive && 0 === count($installedTemplates)) {
60+
$question = new ConfirmationQuestion('No templates are installed. Install all '.count($allTemplates).'?');
61+
$installAll = $io->askQuestion($question);
62+
63+
if ('yes' === $installAll) {
64+
$io->info('Installing all templates...');
65+
$command = new ArrayInput([
66+
'command' => 'app:templates:install',
67+
'--all' => true,
68+
]);
69+
$application->doRun($command, $output);
70+
}
71+
} else {
72+
$io->info('Updating existing template...');
73+
$command = new ArrayInput([
74+
'command' => 'app:templates:update',
75+
]);
76+
$application->doRun($command, $output);
77+
}
78+
79+
return Command::SUCCESS;
80+
}
1281
}

src/Service/TemplateService.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ public function installTemplate(TemplateData $templateData, bool $update = false
4343
$this->entityManager->flush();
4444
}
4545

46+
public function updateTemplate(TemplateData $templateData): void
47+
{
48+
$template = $templateData->templateEntity;
49+
50+
// Ignore templates that do not exist in the database.
51+
if (null === $template) {
52+
return;
53+
}
54+
55+
$template->setTitle($templateData->title);
56+
57+
$this->entityManager->flush();
58+
}
59+
4660
public function getAllTemplates(): array
4761
{
4862
return array_merge($this->getCoreTemplates(), $this->getCustomTemplates());

0 commit comments

Comments
 (0)