diff --git a/Makefile b/Makefile index 5d34265..1866eb7 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,7 @@ ssg.watch: node_modules --signal SIGTERM \ --watch ./app \ --watch ./docs/pages \ - --watch ./resonance \ + --watch ./src \ --watch ./resources \ --exec '$(MAKE) ssg || exit 1' diff --git a/docs/pages/docs/features/security/oauth2/persistent-data/doctrine.md b/docs/pages/docs/features/security/oauth2/persistent-data/doctrine.md deleted file mode 100644 index 7a55a14..0000000 --- a/docs/pages/docs/features/security/oauth2/persistent-data/doctrine.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -collections: - - documents -layout: dm:document -parent: docs/features/security/oauth2/persistent-data/index -title: Doctrine -description: > - Learn how to persist OAuth2 tokens and other data by using Doctrine. ---- - -# Doctrine - -If you are already using {{docs/features/database/doctrine/index}}, you might -want to consider this approach instead of using -{{docs/features/security/oauth2/persistent-data/repositories}}. - -# Usage - -Instead of implementing several repositories, you can implement just the -`OAuth2EntityRepositoryInterface` that is compatible with all the grant types. - -It's primary purpose is to cast OAuth2 data into your application's Doctrine -entities. - -Method | Description --|- -`convertAccessToken` | convert internal access token model into your entity -`convertAuthCode` | convert internal auth code model into your entity -`convertRefreshToken` | convert internal refresh token model into your entity -`findAccessToken` | convert internal access token model into your entity -`findAuthCode` | find auth code in your database, return your entity -`findClient` | find client in your database, return your entity -`findRefreshToken` | find refresh token in your database, return your entity -`findUser` | find user in your database, return your entity -`toAccessToken` | find access token in your database, return your entity -`toClientEntity` | find client in your database, return your entity diff --git a/docs/pages/docs/features/security/oauth2/persistent-data/index.md b/docs/pages/docs/features/security/oauth2/persistent-data/index.md index 0bd3b07..6e93add 100644 --- a/docs/pages/docs/features/security/oauth2/persistent-data/index.md +++ b/docs/pages/docs/features/security/oauth2/persistent-data/index.md @@ -10,4 +10,23 @@ description: > # Persistent Data -{{docs/features/security/oauth2/persistent-data/*!docs/features/security/oauth2/persistent-data/index}} +# Usage + +Instead of implementing several repositories, you can implement just the +`OAuth2EntityRepositoryInterface` that is compatible with all the grant types. + +It's primary purpose is to cast OAuth2 data into your application's Doctrine +entities. + +Method | Description +-|- +`convertAccessToken` | convert internal access token model into your entity +`convertAuthCode` | convert internal auth code model into your entity +`convertRefreshToken` | convert internal refresh token model into your entity +`findAccessToken` | convert internal access token model into your entity +`findAuthCode` | find auth code in your database, return your entity +`findClient` | find client in your database, return your entity +`findRefreshToken` | find refresh token in your database, return your entity +`findUser` | find user in your database, return your entity +`toAccessToken` | find access token in your database, return your entity +`toClientEntity` | find client in your database, return your entity diff --git a/docs/pages/docs/features/timers/cron/index.md b/docs/pages/docs/features/timers/cron/index.md new file mode 100644 index 0000000..dee6267 --- /dev/null +++ b/docs/pages/docs/features/timers/cron/index.md @@ -0,0 +1,54 @@ +--- +collections: + - documents +layout: dm:document +parent: docs/features/timers/index +title: CRON +description: > + Learn how to schedule tasks with CRON notation. +--- + +# CRON + +## Usage + +### Running Scheduler + +You need to invoke the `cron` command: + +```php +$ php bin/resonance.php cron +``` + +That command should be a long-running processs, it *MUST NOT* be executed every +minute, because Resonance has it's own built-in CRON scheduler. + +### Implementing CRON Jobs + +Your class has to implement `CronJobInterface` (or extend `CronJob`) and have +`ScheduledWithCron` attribute. + +It also needs to belong to the `CronJob` collection. + +For example: + +```php + + Learn how to schedule cyclical tasks. +--- + +# Timers + +{{docs/features/timers/*/index}} diff --git a/docs/pages/docs/features/timers/tick-timer/index.md b/docs/pages/docs/features/timers/tick-timer/index.md new file mode 100644 index 0000000..5a542b4 --- /dev/null +++ b/docs/pages/docs/features/timers/tick-timer/index.md @@ -0,0 +1,41 @@ +--- +collections: + - documents +layout: dm:document +parent: docs/features/timers/index +title: Tick Timer +description: > + Learn how to schedule tasks that trigger every N seconds. +--- + +# Tick Timer + +## Usage + +For example: + +```php +executeInCoroutine($input, $output); + $coroutineResult = run(function () use (&$exception, $input, $output, &$result) { + try { + $result = $this->executeInCoroutine($input, $output); + } catch (Throwable $throwable) { + $exception = $throwable; + } }); + if ($exception) { + throw $exception; + } + if (!$coroutineResult) { return Command::FAILURE; } diff --git a/src/DependencyInjectionContainer.php b/src/DependencyInjectionContainer.php index 36fbaf1..ba542bf 100644 --- a/src/DependencyInjectionContainer.php +++ b/src/DependencyInjectionContainer.php @@ -198,11 +198,14 @@ public function registerSingletons(): void foreach ($this->dependencyProviders as $providedClassName => $dependencyProvider) { if ($dependencyProvider->grantsFeature && !$this->wantedFeatures->contains($dependencyProvider->grantsFeature)) { $this->disabledFeatureProviders->put($providedClassName, $dependencyProvider->grantsFeature); - $this->dependencyProviders->remove($providedClassName); } elseif ($dependencyProvider->collection) { $this->addToCollection($dependencyProvider->collection, $dependencyProvider); } } + + foreach ($this->disabledFeatureProviders->keys() as $providedClassName) { + $this->dependencyProviders->remove($providedClassName); + } } private function addToCollection(SingletonCollectionInterface $collectionName, DependencyProvider $dependencyProvider): void diff --git a/src/EsbuildMetaBuilder.php b/src/EsbuildMetaBuilder.php index 0b011d9..3200b85 100644 --- a/src/EsbuildMetaBuilder.php +++ b/src/EsbuildMetaBuilder.php @@ -9,6 +9,7 @@ use Generator; use LogicException; use RuntimeException; +use Swoole\Coroutine; #[Singleton] readonly class EsbuildMetaBuilder @@ -136,7 +137,13 @@ private function getEsbuildMetaContents(string $esbuildMetafile): string throw new RuntimeException('Esbuild meta manifest is not readable: '.$esbuildMetafile); } - return file_get_contents($esbuildMetafile); + $contents = Coroutine::readFile($esbuildMetafile); + + if (!is_string($contents)) { + throw new RuntimeException('Unable to read esbuild manifest: '.$esbuildMetafile); + } + + return $contents; } private function getEsbuildMetaDecoded(string $esbuildMetafile): object diff --git a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php index 765b566..1a7b16c 100644 --- a/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php +++ b/src/SingletonProvider/ConfigurationProvider/OAuth2ConfigurationProvider.php @@ -6,11 +6,14 @@ use Defuse\Crypto\Key; use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\OAuth2Configuration; use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider; use League\OAuth2\Server\CryptKey; use Nette\Schema\Expect; use Nette\Schema\Schema; +use RuntimeException; +use Swoole\Coroutine; /** * @template-extends ConfigurationProvider */ -#[Singleton(provides: OAuth2Configuration::class)] +#[Singleton( + grantsFeature: Feature::OAuth2, + provides: OAuth2Configuration::class, +)] final readonly class OAuth2ConfigurationProvider extends ConfigurationProvider { protected function getConfigurationKey(): string @@ -46,8 +52,14 @@ protected function getSchema(): Schema protected function provideConfiguration($validatedData): OAuth2Configuration { + $encryptionKeyContent = Coroutine::readFile($validatedData->encryption_key); + + if (!is_string($encryptionKeyContent)) { + throw new RuntimeException('Unable to read encrpytion key file: '.$validatedData->encryption_key); + } + return new OAuth2Configuration( - encryptionKey: Key::loadFromAsciiSafeString(file_get_contents($validatedData->encryption_key)), + encryptionKey: Key::loadFromAsciiSafeString($encryptionKeyContent), jwtSigningKeyPrivate: new CryptKey( DM_ROOT.'/'.$validatedData->jwt_signing_key_private, $validatedData->jwt_signing_key_passphrase, diff --git a/src/StaticPageLayoutAggregate.php b/src/StaticPageLayoutAggregate.php index 06cfaac..2cb23ef 100644 --- a/src/StaticPageLayoutAggregate.php +++ b/src/StaticPageLayoutAggregate.php @@ -6,6 +6,7 @@ use Ds\Map; use Generator; +use Throwable; readonly class StaticPageLayoutAggregate { @@ -24,10 +25,17 @@ public function __construct() */ public function render(StaticPage $staticPage): Generator { - yield from $this - ->selectLayout($staticPage) - ->renderStaticPage($staticPage) - ; + try { + yield from $this + ->selectLayout($staticPage) + ->renderStaticPage($staticPage) + ; + } catch (Throwable $throwable) { + throw new StaticPageRenderingException(sprintf( + 'Error while rendering static page: %s', + $staticPage->getBasename(), + ), 0, $throwable); + } } public function selectLayout(StaticPage $staticPage): StaticPageLayoutInterface