diff --git a/temporal/.editorconfig b/temporal/.editorconfig new file mode 100644 index 000000000..5e9a93ea5 --- /dev/null +++ b/temporal/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/temporal/.gitattributes b/temporal/.gitattributes new file mode 100644 index 000000000..2daf9d365 --- /dev/null +++ b/temporal/.gitattributes @@ -0,0 +1,27 @@ +# Autodetect text files +* text=auto + +# ...Unless the name matches the following overriding patterns + +# Definitively text files +*.php text +*.css text +*.js text +*.txt text +*.md text +*.xml text +*.json text +*.bat text +*.sql text +*.yml text + +# Ensure those won't be messed up with +*.png binary +*.jpg binary +*.gif binary +*.ttf binary + +# Avoid merge conflicts in CHANGELOG +# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/ +/CHANGELOG.md merge=union + diff --git a/temporal/.github/CODE_OF_CONDUCT.md b/temporal/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..803e0007b --- /dev/null +++ b/temporal/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,67 @@ +# Yii Contributor Code of Conduct + +## Our Pledge + +As contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to respect all community members. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Personal attacks +* Trolling or insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other conduct which could reasonably be considered inappropriate in + a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in response +to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when +an individual is representing the project or its community. Examples of representing +a project or community include posting via an official social media account, +within project GitHub, official forum or acting as an appointed representative at +an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported +by contacting core team members. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the circumstances. +The project team is obligated to maintain confidentiality with regard to the reporter of +an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith +may face temporary or permanent repercussions as determined by other members of +the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4.0, available at +[http://contributor-covenant.org/version/1/4/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/temporal/.github/CONTRIBUTING.md b/temporal/.github/CONTRIBUTING.md new file mode 100644 index 000000000..28737bdfa --- /dev/null +++ b/temporal/.github/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Prerequisites + +- [Yii goal and values](https://github.com/yiisoft/docs/blob/master/001-yii-values.md) +- [Namespaces](https://github.com/yiisoft/docs/blob/master/004-namespaces.md) +- [Git commit messages](https://github.com/yiisoft/docs/blob/master/006-git-commit-messages.md) +- [Exceptions](https://github.com/yiisoft/docs/blob/master/007-exceptions.md) +- [Interfaces](https://github.com/yiisoft/docs/blob/master/008-interfaces.md) + +# Getting started + +Since Yii 3 consists of many packages, we have a [special development tool](https://github.com/yiisoft/docs/blob/master/005-development-tool.md). + +1. [Clone the repository](https://github.com/yiisoft/yii-dev-tool). + +2. [Set up your own fork](https://github.com/yiisoft/yii-dev-tool#using-your-own-fork). + +3. Now you are ready. Fork any package listed in `packages.php` and do `./yii-dev install username/package`. + +If you don't have any particular package in mind to start with: + +- [Check roadmap](https://github.com/yiisoft/docs/blob/master/003-roadmap.md). +- Check package issues at github. Usually there are some. +- Ask @samdark. diff --git a/temporal/.github/FUNDING.yml b/temporal/.github/FUNDING.yml new file mode 100644 index 000000000..f0dc5312d --- /dev/null +++ b/temporal/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +open_collective: yiisoft +github: [yiisoft] diff --git a/temporal/.github/ISSUE_TEMPLATE.md b/temporal/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..b748c2dac --- /dev/null +++ b/temporal/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ + + +### What steps will reproduce the problem? + +### What is the expected result? + +### What do you get instead? + + +### Additional info + +| Q | A +| ---------------- | --- +| Version | 1.0.? +| PHP version | +| Operating system | diff --git a/temporal/.github/PULL_REQUEST_TEMPLATE.md b/temporal/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..4a3e8ace9 --- /dev/null +++ b/temporal/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +| Q | A +| ------------- | --- +| Is bugfix? | ✔️/❌ +| New feature? | ✔️/❌ +| Breaks BC? | ✔️/❌ +| Fixed issues | comma-separated list of tickets # fixed by the PR, if any diff --git a/temporal/.github/SECURITY.md b/temporal/.github/SECURITY.md new file mode 100644 index 000000000..ba0931832 --- /dev/null +++ b/temporal/.github/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +Please use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you +find in Yii. DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help. + +Please note that as a non-commercial OpenSource project we are not able to pay bounties at the moment. diff --git a/temporal/.github/dependabot.yml b/temporal/.github/dependabot.yml new file mode 100644 index 000000000..d7ebdbfdb --- /dev/null +++ b/temporal/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + # Too noisy. See https://github.community/t/increase-if-necessary-for-github-actions-in-dependabot/179581 + open-pull-requests-limit: 0 + + # Maintain dependencies for Composer + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" + versioning-strategy: increase-if-necessary diff --git a/temporal/.github/workflows/build.yml b/temporal/.github/workflows/build.yml new file mode 100644 index 000000000..20e1a0341 --- /dev/null +++ b/temporal/.github/workflows/build.yml @@ -0,0 +1,63 @@ +on: + - pull_request + - push + +name: build + +jobs: + tests: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + env: + extensions: fileinfo, pdo, pdo_sqlite + key: cache-v1 + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + + php: + - "8.0" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + ini-values: date.timezone='UTC' + coverage: pcov + tools: composer:v2 + + - name: Determine composer cache directory on Linux + if: matrix.os == 'ubuntu-latest' + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Determine composer cache directory on Windows + if: matrix.os == 'windows-latest' + run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Cache dependencies installed with composer + uses: actions/cache@v2 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + - name: Update composer + run: composer self-update + + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run tests with codeception + run: | + php -S 127.0.0.1:8080 -t public public/index-test.php > ./runtime/yii.log 2>&1 & vendor/bin/codecept run acceptance --env github-ci diff --git a/temporal/.github/workflows/static.yml b/temporal/.github/workflows/static.yml new file mode 100644 index 000000000..6d2ee4b09 --- /dev/null +++ b/temporal/.github/workflows/static.yml @@ -0,0 +1,50 @@ +on: + - pull_request + - push + +name: static analysis + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.0" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + tools: composer:v2, cs2pr + coverage: none + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v2 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + - name: Update composer + run: composer self-update + + - name: Install dependencies with composer + run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Static analysis + run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle | cs2pr --graceful-warnings --colorize diff --git a/temporal/.gitignore b/temporal/.gitignore new file mode 100644 index 000000000..15fee4e28 --- /dev/null +++ b/temporal/.gitignore @@ -0,0 +1,40 @@ +# phpstorm project files +.idea + +# netbeans project files +nbproject + +# zend studio for eclipse project files +.buildpath +.project +.settings + +# windows thumbnail cache +Thumbs.db + +# composer vendor dir +/vendor + +/composer.lock + +# composer itself is not needed +composer.phar + +# Mac DS_Store Files +.DS_Store + +# phpunit itself is not needed +phpunit.phar +# local phpunit config +/phpunit.xml +# phpunit cache +.phpunit.result.cache + +# Codeception +c3.php + +#tests +tests/_data/database.db_snapshot + +.env +rr diff --git a/temporal/.phpunit-watcher.yml b/temporal/.phpunit-watcher.yml new file mode 100644 index 000000000..0e4d76634 --- /dev/null +++ b/temporal/.phpunit-watcher.yml @@ -0,0 +1,11 @@ +watch: + directories: + - src + - tests + fileMask: '*.php' +notifications: + passingTests: false + failingTests: false +phpunit: + binaryPath: vendor/bin/phpunit + timeout: 180 diff --git a/temporal/.rr.yaml b/temporal/.rr.yaml new file mode 100644 index 000000000..2cc5076fd --- /dev/null +++ b/temporal/.rr.yaml @@ -0,0 +1,47 @@ +# Rich config example you may look at here +# https://github.com/roadrunner-server/roadrunner/blob/master/.rr.yaml + +rpc: + listen: tcp://0.0.0.0:6001 + +server: + command: "php -dxdebug.mode=debug public/index.php" + # command: "php public/index.php" + +http: + address: 0.0.0.0:8080 + middleware: [ "static", "gzip" ] + pool: + num_workers: 2 + uploads: + forbid: [ ".php", ".exe", ".bat" ] + static: + dir: "public" + forbid: [ ".php", ".htaccess" ] + +temporal: + address: 0.0.0.0:7233 + activities: + num_workers: 5 + +logs: + mode: development + channels: + http: + level: debug # Log all http requests, set to info to disable + server: + level: info # Everything written to worker stderr is logged + mode: raw + metrics: + level: debug + temporal: + level: info + +reload: + enabled: true + interval: 1s + patterns: [".php", ".yaml"] + services: + http: + dirs: ["."] + recursive: true diff --git a/temporal/.scrutinizer.yml b/temporal/.scrutinizer.yml new file mode 100644 index 000000000..b53476e64 --- /dev/null +++ b/temporal/.scrutinizer.yml @@ -0,0 +1,38 @@ +checks: + php: true + +filter: + paths: + - "src/*" + excluded_paths: + - "src/ApplicationRunner.php" + +build: + nodes: + analysis: + environment: + php: 8.0.6 + + tests: + override: + - php-scrutinizer-run + + tests-and-coverage: + environment: + php: + version: 8.0.6 + ini: + xdebug.mode: coverage + + dependencies: + override: + - composer self-update + - composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + tests: + override: + - command: php -S 127.0.0.1:8080 -t public public/index-test.php > ./runtime/yii.log 2>&1 & vendor/bin/codecept run acceptance --coverage-xml --env github-ci + on_node: 1 + coverage: + file: tests/_output/coverage.xml + format: php-clover diff --git a/temporal/.styleci.yml b/temporal/.styleci.yml new file mode 100644 index 000000000..81b065005 --- /dev/null +++ b/temporal/.styleci.yml @@ -0,0 +1,89 @@ +preset: psr12 +risky: true + +version: 8 + +finder: + exclude: + - docs + - vendor + - resources + - views + - public + - templates + not-name: + - UnionCar.php + - TimerUnionTypes.php + - schema1.php + +enabled: + - alpha_ordered_traits + - array_indentation + - array_push + - combine_consecutive_issets + - combine_consecutive_unsets + - combine_nested_dirname + - declare_strict_types + - dir_constant + - final_static_access + - fully_qualified_strict_types + - function_to_constant + - hash_to_slash_comment + - is_null + - logical_operators + - magic_constant_casing + - magic_method_casing + - method_separation + - modernize_types_casting + - native_function_casing + - native_function_type_declaration_casing + - no_alias_functions + - no_empty_comment + - no_empty_phpdoc + - no_empty_statement + - no_extra_block_blank_lines + - no_short_bool_cast + - no_superfluous_elseif + - no_unneeded_control_parentheses + - no_unneeded_curly_braces + - no_unneeded_final_method + - no_unset_cast + - no_unused_imports + - no_unused_lambda_imports + - no_useless_else + - no_useless_return + - normalize_index_brace + - php_unit_dedicate_assert + - php_unit_dedicate_assert_internal_type + - php_unit_expectation + - php_unit_mock + - php_unit_mock_short_will_return + - php_unit_namespaced + - php_unit_no_expectation_annotation + - phpdoc_no_empty_return + - phpdoc_no_useless_inheritdoc + - phpdoc_order + - phpdoc_property + - phpdoc_scalar + - phpdoc_separation + - phpdoc_singular_inheritdoc + - phpdoc_trim + - phpdoc_trim_consecutive_blank_line_separation + - phpdoc_type_to_var + - phpdoc_types + - phpdoc_types_order + - print_to_echo + - regular_callable_call + - return_assignment + - self_accessor + - self_static_accessor + - set_type_to_cast + - short_array_syntax + - short_list_syntax + - simplified_if_return + - single_quote + - standardize_not_equals + - ternary_to_null_coalescing + - trailing_comma_in_multiline_array + - unalign_double_arrow + - unalign_equals diff --git a/temporal/CHANGELOG.md b/temporal/CHANGELOG.md new file mode 100644 index 000000000..6a8fbfeca --- /dev/null +++ b/temporal/CHANGELOG.md @@ -0,0 +1,5 @@ +# _____ Change Log + +## 1.0.0 under development + +- Initial release. diff --git a/temporal/LICENSE.md b/temporal/LICENSE.md new file mode 100644 index 000000000..bc5674fe4 --- /dev/null +++ b/temporal/LICENSE.md @@ -0,0 +1,29 @@ +Copyright © 2008 by Yii Software (https://www.yiiframework.com/) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/temporal/README.md b/temporal/README.md new file mode 100644 index 000000000..fc3d58aa2 --- /dev/null +++ b/temporal/README.md @@ -0,0 +1,92 @@ +# Introduction + +This is demo application to show how to work with [Temporal](https://github.com/temporalio/sdk-php) from Yii 3 application. + +## Installation +Install PHP dependencies: + +```shell +composer i --prefer-dist +``` + +Get `RoadRunner` binary: + +```shell +./vendor/bin/rr get-binary +``` + +Run docker containers: + +```shell +docker-compose up -d +``` + +Run `RoadRunner`: + +```shell +./rr serve -d +``` + +## Usage + +Now you can open [http://localhost:8080](http://localhost:8080/) +and see small description about examples: + +``` +This project is example how to use Temporal with Yii 3 application. +There are exist several examples how it works. +Examples: +If you want to see how "simple" workflow works click here. +There is usual call non-blocking action. It should work as faster as you can run usual php code. +If you want to see how "complicated" workflow works click here. +There are imitation for blocking action. Different methods calls will run with random delay: from 1 to 5 seconds per call. +If you want to see how "deferred" workflow works click here. +There are imitation for asynchronous action. You will get "job id" and you can track the status in another endpoint. +Logic for workflow will be same as "complicated" workflow. +``` + +Also, you can open Temporal admin panel the located in [http://localhost:8088](http://localhost:8088/) + +--- + +To add more **workflows** and **activities** you need to configure them in [config/common/temporal.php](config/common/temporal.php): +You should add tag `temporal.workflow` for each new workflow and `temporal.activity` for each new activity. + +Example: +```php +\App\Temporal\Workflow\LongWorkflow::class => [ + 'class' => \App\Temporal\Workflow\LongWorkflow::class, + 'tags' => ['temporal.workflow'] +], + +\App\Temporal\Activity\CommonActivity::class => [ + 'class' => \App\Temporal\Activity\CommonActivity::class, + 'tags' => ['temporal.activity'] +], +``` + +After that the workflows and activities will be automatically registered in [src/ApplicationRunner.php](src/ApplicationRunner.php) + +## Contribution + +You are welcome to add more examples, fix bugs or orthographic errors. + +## Useful links + +#### Used libraries +[Temporal PHP SDK](https://github.com/temporalio/sdk-php) +[RoadRunner](https://github.com/spiral/roadrunner) + +#### Temporal: +[Temporal (temporal.io)](https://temporal.io/) + +#### Workshops +1st part [Оркестрируй это! Описываем сложные бизнес процессы на PHP - Антон Титов](https://www.youtube.com/watch?v=0NCMEaFMj_M) + +2nd part [Оркестрация и закон Мерфи: обрабатываем ошибки-бизнес процессов](https://www.youtube.com/watch?v=upL8o-OXYEc) + +#### Application (after run on local machine) +[This application demo](http://localhost:8080/) + +[Temporal admin panel](http://localhost:8088/) + diff --git a/temporal/autoload.php b/temporal/autoload.php new file mode 100644 index 000000000..2e5eee2a3 --- /dev/null +++ b/temporal/autoload.php @@ -0,0 +1,20 @@ +load(); + +$_ENV['YII_ENV'] = empty($_ENV['YII_ENV']) ? null : (string)$_ENV['YII_ENV']; +$_SERVER['YII_ENV'] = $_ENV['YII_ENV']; + +$_ENV['YII_DEBUG'] = filter_var( + !empty($_ENV['YII_DEBUG']) ? $_ENV['YII_DEBUG'] : true, + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE +) ?? true; +$_SERVER['YII_DEBUG'] = $_ENV['YII_DEBUG']; diff --git a/temporal/codeception.yml b/temporal/codeception.yml new file mode 100644 index 000000000..78b2e645c --- /dev/null +++ b/temporal/codeception.yml @@ -0,0 +1,17 @@ +namespace: App\Tests +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +actor_suffix: Tester +extensions: + enabled: + - Codeception\Extension\RunFailed +coverage: + enabled: true + c3_url: http://127.0.0.1:8080 + whitelist: + include: + - src/* diff --git a/temporal/composer.json b/temporal/composer.json new file mode 100644 index 000000000..a8cf073e5 --- /dev/null +++ b/temporal/composer.json @@ -0,0 +1,183 @@ +{ + "name": "yiisoft/app-api", + "type": "project", + "description": "Yii Framework API project template", + "keywords": [ + "yii3", + "app", + "api", + "rest" + ], + "homepage": "https://github.com/yiisoft/app-api/", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/app-api/issues?state=open", + "forum": "https://www.yiiframework.com/forum/", + "wiki": "https://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/app-api" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": "^8.0", + "cebe/markdown": "^1.2", + "cycle/orm": "^v2.2", + "httpsoft/http-message": "^1.0", + "psr/container": "^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^3.0", + "spiral/roadrunner": "^v2.11", + "temporal/sdk": "^2.3", + "vlucas/phpdotenv": "^v5.5", + "yiisoft/access": "^1.0", + "yiisoft/aliases": "^2.0", + "yiisoft/auth": "^3.0", + "yiisoft/cache": "^2.0", + "yiisoft/cache-file": "^2.0", + "yiisoft/classifier": "dev-master", + "yiisoft/config": "^1.1", + "yiisoft/data": "dev-master", + "yiisoft/data-response": "^1.0", + "yiisoft/definitions": "^2.0", + "yiisoft/di": "^1.1", + "yiisoft/error-handler": "^2.1", + "yiisoft/factory": "^1.0", + "yiisoft/files": "^2.0", + "yiisoft/http": "^1.2", + "yiisoft/injector": "^1.1", + "yiisoft/log": "^2.0", + "yiisoft/log-target-file": "^2.0", + "yiisoft/request-body-parser": "^1.1", + "yiisoft/request-model": "dev-master", + "yiisoft/router": "^1.2", + "yiisoft/router-fastroute": "^1.1", + "yiisoft/security": "^1.0", + "yiisoft/user": "^1.0", + "yiisoft/validator": "dev-master", + "yiisoft/yii-console": "^1.3", + "yiisoft/yii-cycle": "dev-master", + "yiisoft/yii-event": "^1.0", + "yiisoft/yii-http": "^1.0", + "yiisoft/yii-middleware": "dev-master", + "yiisoft/yii-runner-console": "^1.1", + "yiisoft/yii-runner-roadrunner": "dev-support-temporal", + "yiisoft/yii-swagger": "^1.2" + }, + "require-dev": { + "codeception/c3": "^2.6.0", + "codeception/codeception": "^5.0", + "codeception/lib-innerbrowser": "^3.1", + "codeception/module-asserts": "^3.0", + "codeception/module-db": "^3.0", + "codeception/module-phpbrowser": "^3.0", + "codeception/module-rest": "^3.3", + "phpunit/phpunit": "^9.5", + "roave/infection-static-analysis-plugin": "^1.25", + "spatie/phpunit-watcher": "^1.23", + "vimeo/psalm": "^4.29", + "yiisoft/json": "^1.0", + "yiisoft/yii-debug": "3.0.x-dev", + "yiisoft/yii-debug-api": "3.0.x-dev" + }, + "autoload": { + "psr-4": { + "App\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/", + "App\\Tests\\Acceptance\\": "tests/acceptance/", + "App\\Tests\\Functional\\": "tests/functional/" + } + }, + "scripts": { + "serve": "./yii serve", + "post-update-cmd": [ + "App\\Installer::postUpdate" + ], + "test": "phpunit --testdox --no-interaction", + "test-watch": "phpunit-watcher watch" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + }, + "config-plugin-options": { + "source-directory": "config" + }, + "config-plugin": { + "common": "common/*.php", + "params": [ + "params.php", + "?params-local.php" + ], + "web": [ + "$common", + "web/*.php" + ], + "console": [ + "$common", + "console/*.php" + ], + "events": "events.php", + "events-web": [ + "$events", + "events-web.php" + ], + "events-console": [ + "$events", + "events-console.php" + ], + "providers": "providers.php", + "providers-web": [ + "$providers", + "providers-web.php" + ], + "providers-console": [ + "$providers", + "providers-console.php" + ], + "delegates": "delegates.php", + "delegates-web": [ + "$delegates", + "delegates-web.php" + ], + "delegates-console": [ + "$delegates", + "delegates-console.php" + ], + "routes": "routes.php", + "bootstrap": "bootstrap.php", + "bootstrap-web": [ + "$bootstrap", + "bootstrap-web.php" + ], + "bootstrap-console": [ + "$bootstrap", + "bootstrap-console.php" + ], + "tags": "tags.php", + "tags-web": [ + "$tags", + "tags-web.php" + ], + "tags-console": [ + "$tags", + "tags-console.php" + ] + } + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "codeception/c3": true, + "infection/extension-installer": true, + "yiisoft/config": true + } + } +} diff --git a/temporal/config/.gitignore b/temporal/config/.gitignore new file mode 100644 index 000000000..8268b94c1 --- /dev/null +++ b/temporal/config/.gitignore @@ -0,0 +1 @@ +*-local.php diff --git a/temporal/config/.merge-plan.php b/temporal/config/.merge-plan.php new file mode 100644 index 000000000..9693c6632 --- /dev/null +++ b/temporal/config/.merge-plan.php @@ -0,0 +1,337 @@ + [ + 'common' => [ + 'yiisoft/cache-file' => [ + 'config/common.php', + ], + 'yiisoft/router-fastroute' => [ + 'config/common.php', + ], + 'yiisoft/yii-cycle' => [ + 'config/common.php', + ], + 'yiisoft/yii-runner-roadrunner' => [ + 'config/common/temporal.php', + ], + 'yiisoft/validator' => [ + 'config/common.php', + ], + 'yiisoft/router' => [ + 'config/common.php', + ], + 'yiisoft/yii-debug' => [ + 'config/common.php', + ], + 'yiisoft/profiler' => [ + 'config/common.php', + ], + 'yiisoft/yii-filesystem' => [ + 'config/common.php', + ], + 'yiisoft/yii-queue' => [ + 'config/common.php', + ], + 'yiisoft/aliases' => [ + 'config/common.php', + ], + 'yiisoft/log-target-file' => [ + 'config/common.php', + ], + 'yiisoft/translator' => [ + 'config/common.php', + ], + 'yiisoft/yii-event' => [ + 'config/common.php', + ], + 'yiisoft/view' => [ + 'config/common.php', + ], + 'yiisoft/cache' => [ + 'config/common.php', + ], + '/' => [ + 'common/*.php', + ], + ], + 'params' => [ + 'yiisoft/cache-file' => [ + 'config/params.php', + ], + 'yiisoft/router-fastroute' => [ + 'config/params.php', + ], + 'yiisoft/user' => [ + 'config/params.php', + ], + 'yiisoft/yii-cycle' => [ + 'config/params.php', + ], + 'yiisoft/yii-runner-roadrunner' => [ + 'config/params.php', + ], + 'yiisoft/yii-swagger' => [ + 'config/params.php', + ], + 'yiisoft/yii-debug-api' => [ + 'config/params.php', + ], + 'yiisoft/validator' => [ + 'config/params.php', + ], + 'yiisoft/assets' => [ + 'config/params.php', + ], + 'yiisoft/yii-view' => [ + 'config/params.php', + ], + 'yiisoft/yii-debug' => [ + 'config/params.php', + ], + 'yiisoft/profiler' => [ + 'config/params.php', + ], + 'yiisoft/yii-queue' => [ + 'config/params.php', + ], + 'yiisoft/aliases' => [ + 'config/params.php', + ], + 'yiisoft/data-response' => [ + 'config/params.php', + ], + 'yiisoft/log-target-file' => [ + 'config/params.php', + ], + 'yiisoft/translator' => [ + 'config/params.php', + ], + 'yiisoft/yii-console' => [ + 'config/params.php', + ], + 'yiisoft/csrf' => [ + 'config/params.php', + ], + 'yiisoft/view' => [ + 'config/params.php', + ], + 'yiisoft/session' => [ + 'config/params.php', + ], + '/' => [ + 'params.php', + '?params-local.php', + ], + ], + 'web' => [ + 'yiisoft/router-fastroute' => [ + 'config/web.php', + ], + 'yiisoft/user' => [ + 'config/web.php', + ], + 'yiisoft/yii-swagger' => [ + 'config/web.php', + ], + 'yiisoft/yii-debug-api' => [ + 'config/web.php', + ], + 'yiisoft/middleware-dispatcher' => [ + 'config/web.php', + ], + 'yiisoft/assets' => [ + 'config/web.php', + ], + 'yiisoft/yii-view' => [ + 'config/web.php', + ], + 'yiisoft/yii-debug' => [ + 'config/web.php', + ], + 'yiisoft/data-response' => [ + 'config/web.php', + ], + 'yiisoft/error-handler' => [ + 'config/web.php', + ], + 'yiisoft/yii-event' => [ + 'config/web.php', + ], + 'yiisoft/csrf' => [ + 'config/web.php', + ], + 'yiisoft/view' => [ + 'config/web.php', + ], + 'yiisoft/session' => [ + 'config/web.php', + ], + '/' => [ + '$common', + 'web/*.php', + ], + ], + 'console' => [ + 'yiisoft/yii-cycle' => [ + 'config/console.php', + ], + 'yiisoft/yii-debug' => [ + 'config/console.php', + ], + 'yiisoft/yii-console' => [ + 'config/console.php', + ], + 'yiisoft/yii-event' => [ + 'config/console.php', + ], + '/' => [ + '$common', + 'console/*.php', + ], + ], + 'events-console' => [ + 'yiisoft/yii-cycle' => [ + 'config/events-console.php', + ], + 'yiisoft/yii-debug' => [ + 'config/events-console.php', + ], + 'yiisoft/log' => [ + 'config/events-console.php', + ], + 'yiisoft/yii-console' => [ + 'config/events-console.php', + ], + 'yiisoft/yii-event' => [ + '$events', + 'config/events-console.php', + ], + '/' => [ + '$events', + 'events-console.php', + ], + ], + 'delegates' => [ + 'yiisoft/yii-cycle' => [ + 'config/delegates.php', + ], + '/' => [ + 'delegates.php', + ], + ], + 'bootstrap-web' => [ + 'yiisoft/yii-debug-api' => [ + 'config/bootstrap-web.php', + ], + '/' => [ + '$bootstrap', + 'bootstrap-web.php', + ], + ], + 'routes' => [ + 'yiisoft/yii-debug-api' => [ + 'config/routes.php', + ], + '/' => [ + 'routes.php', + ], + ], + 'providers-web' => [ + 'yiisoft/yii-debug-api' => [ + 'config/providers-web.php', + ], + '/' => [ + '$providers', + 'providers-web.php', + ], + ], + 'providers' => [ + 'yiisoft/yii-debug' => [ + 'config/providers.php', + ], + '/' => [ + 'providers.php', + ], + ], + 'events-web' => [ + 'yiisoft/yii-debug' => [ + 'config/events-web.php', + ], + 'yiisoft/profiler' => [ + 'config/events-web.php', + ], + 'yiisoft/log' => [ + 'config/events-web.php', + ], + 'yiisoft/yii-event' => [ + '$events', + 'config/events-web.php', + ], + '/' => [ + '$events', + 'events-web.php', + ], + ], + 'providers-console' => [ + 'yiisoft/yii-console' => [ + 'config/providers-console.php', + ], + '/' => [ + '$providers', + 'providers-console.php', + ], + ], + 'events' => [ + 'yiisoft/yii-event' => [ + 'config/events.php', + ], + '/' => [ + 'events.php', + ], + ], + 'delegates-web' => [ + '/' => [ + '$delegates', + 'delegates-web.php', + ], + ], + 'delegates-console' => [ + '/' => [ + '$delegates', + 'delegates-console.php', + ], + ], + 'bootstrap' => [ + '/' => [ + 'bootstrap.php', + ], + ], + 'bootstrap-console' => [ + '/' => [ + '$bootstrap', + 'bootstrap-console.php', + ], + ], + 'tags' => [ + '/' => [ + 'tags.php', + ], + ], + 'tags-web' => [ + '/' => [ + '$tags', + 'tags-web.php', + ], + ], + 'tags-console' => [ + '/' => [ + '$tags', + 'tags-console.php', + ], + ], + ], +]; diff --git a/temporal/config/bootstrap-console.php b/temporal/config/bootstrap-console.php new file mode 100644 index 000000000..0dae23dee --- /dev/null +++ b/temporal/config/bootstrap-console.php @@ -0,0 +1,5 @@ + IdMessageReader::class, + \Yiisoft\Cache\CacheInterface::class => \Yiisoft\Cache\Cache::class, + \Psr\SimpleCache\CacheInterface::class => \Yiisoft\Cache\File\FileCache::class, +]; diff --git a/temporal/config/common/logger.php b/temporal/config/common/logger.php new file mode 100644 index 000000000..b49b94ba0 --- /dev/null +++ b/temporal/config/common/logger.php @@ -0,0 +1,21 @@ + [ + 'class' => Logger::class, + '__construct()' => [ + 'targets' => ReferencesArray::from([ + FileTarget::class, + ]), + ], + ], +]; diff --git a/temporal/config/common/psr17.php b/temporal/config/common/psr17.php new file mode 100644 index 000000000..9787bb293 --- /dev/null +++ b/temporal/config/common/psr17.php @@ -0,0 +1,25 @@ + RequestFactory::class, + ServerRequestFactoryInterface::class => ServerRequestFactory::class, + ResponseFactoryInterface::class => ResponseFactory::class, + StreamFactoryInterface::class => StreamFactory::class, + UriFactoryInterface::class => UriFactory::class, + UploadedFileFactoryInterface::class => UploadedFileFactory::class, +]; diff --git a/temporal/config/common/router.php b/temporal/config/common/router.php new file mode 100644 index 000000000..64b970033 --- /dev/null +++ b/temporal/config/common/router.php @@ -0,0 +1,26 @@ + static function (RouteCollectorInterface $collector) use ($config) { + $collector + ->middleware(FormatDataResponse::class) + ->middleware(ExceptionMiddleware::class) + ->middleware(RequestBodyParser::class) + ->addGroup(Group::create()->routes(...$config->get('routes'))); + + return new RouteCollection($collector); + }, +]; diff --git a/temporal/config/common/temporal.php b/temporal/config/common/temporal.php new file mode 100644 index 000000000..93ecbfcf6 --- /dev/null +++ b/temporal/config/common/temporal.php @@ -0,0 +1,11 @@ + WorkflowClient::create(ServiceClient::create('localhost:7233')), +]; diff --git a/temporal/config/console/.gitkeep b/temporal/config/console/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/temporal/config/delegates-console.php b/temporal/config/delegates-console.php new file mode 100644 index 000000000..0dae23dee --- /dev/null +++ b/temporal/config/delegates-console.php @@ -0,0 +1,5 @@ + 'support@example.com', + 'middlewares' => [ + ErrorCatcher::class, + SubFolder::class, + Router::class, + ], + + 'yiisoft/aliases' => [ + 'aliases' => [ + '@root' => dirname(__DIR__), + '@assets' => '@public/assets', + '@assetsUrl' => '@baseUrl/assets', + '@baseUrl' => '/', + '@data' => '@root/data', + '@public' => '@root/public', + '@resources' => '@root/resources', + '@runtime' => '@root/runtime', + '@src' => '@root/src', + '@tests' => '@root/tests', + '@views' => '@root/views', + '@vendor' => '@root/vendor', + ], + ], + + 'yiisoft/router-fastroute' => [ + 'enableCache' => false, + ], + + // Console commands + 'yiisoft/yii-console' => [ + 'commands' => [ + 'cycle/schema' => Schema\SchemaCommand::class, + 'cycle/schema/php' => Schema\SchemaPhpCommand::class, + 'cycle/schema/clear' => Schema\SchemaClearCommand::class, + 'cycle/schema/rebuild' => Schema\SchemaRebuildCommand::class, + 'migrate/create' => Migration\CreateCommand::class, + 'migrate/generate' => Migration\GenerateCommand::class, + 'migrate/up' => Migration\UpCommand::class, + 'migrate/down' => Migration\DownCommand::class, + 'migrate/list' => Migration\ListCommand::class, + ], + ], + + 'yiisoft/yii-cycle' => [ + // DBAL config + 'dbal' => [ + // SQL query logger. Definition of Psr\Log\LoggerInterface + 'query-logger' => null, + // Default database + 'default' => 'default', + 'aliases' => [], + 'databases' => [ + 'default' => ['connection' => 'sqlite'], + ], + 'connections' => [ + 'sqlite' => [ + 'driver' => SQLiteDriver::class, + 'connection' => $_ENV['YII_ENV'] === 'production' + ? 'sqlite:@data/db/database.db' + : 'sqlite:@tests/_data/database.db', + 'username' => '', + 'password' => '', + ], + ], + ], + + // Cycle migration config + 'migrations' => [ + 'directory' => '@root/migrations', + 'namespace' => 'App\\Migration', + 'table' => 'migration', + 'safe' => false, + ], + + /** + * SchemaProvider list for {@see \Yiisoft\Yii\Cycle\Schema\Provider\Support\SchemaProviderPipeline} + * Array of classname and {@see SchemaProviderInterface} object. + * You can configure providers if you pass classname as key and parameters as array: + * [ + * SimpleCacheSchemaProvider::class => [ + * 'key' => 'my-custom-cache-key' + * ], + * FromFilesSchemaProvider::class => [ + * 'files' => ['@runtime/cycle-schema.php'] + * ], + * FromConveyorSchemaProvider::class => [ + * 'generators' => [ + * Generator\SyncTables::class, // sync table changes to database + * ] + * ], + * ] + */ + 'schema-providers' => [ + // Uncomment next line to enable schema cache + // SimpleCacheSchemaProvider::class => ['key' => 'cycle-orm-cache-key'], + FromConveyorSchemaProvider::class => [ + 'generators' => [ + Generator\SyncTables::class, + ], + ], + ], + + /** + * Config for {@see \Yiisoft\Yii\Cycle\Schema\Conveyor\AnnotatedSchemaConveyor} + * Annotated entity directories list. + * {@see \Yiisoft\Aliases\Aliases} are also supported. + */ + 'annotated-entity-paths' => [ + '@src', + ], + ], + 'yiisoft/yii-swagger' => [ + 'annotation-paths' => [ + '@src', + ], + ], + + 'yiisoft/yii-runner-roadrunner' => [ + 'temporal' => [ + 'enabled' => true, + ], + ], +]; diff --git a/temporal/config/providers-console.php b/temporal/config/providers-console.php new file mode 100644 index 000000000..0dae23dee --- /dev/null +++ b/temporal/config/providers-console.php @@ -0,0 +1,5 @@ +action([\App\Controller\HomeController::class, 'index']) + ->name('home'), + + Route::get('/simple/{name:\w+}') + ->action([\App\Controller\WorkflowController::class, 'simpleAction']) + ->name('temporal/simple'), + + Route::get('/complicated/{name:\w+}') + ->action([\App\Controller\WorkflowController::class, 'complicatedAction']) + ->name('temporal/complicated'), + + Route::get('/asynchronous/{name:\w+}') + ->action([\App\Controller\WorkflowController::class, 'asynchronousAction']) + ->name('temporal/asynchronous'), + + Route::get('/asynchronous-status/{id:[\w-]+}') + ->action([\App\Controller\WorkflowController::class, 'asynchronousStatusAction']) + ->name('temporal/asynchronous-status'), + + // Swagger routes + Group::create('/docs') + ->routes( + Route::get('') + ->middleware(FormatDataResponseAsHtml::class) + ->action(fn (SwaggerUi $swaggerUi) => $swaggerUi->withJsonUrl('/docs/openapi.json')), + Route::get('/openapi.json') + ->middleware(FormatDataResponseAsJson::class) + ->action(SwaggerJson::class), + ), +]; diff --git a/temporal/config/tags-console.php b/temporal/config/tags-console.php new file mode 100644 index 000000000..1f6a841dc --- /dev/null +++ b/temporal/config/tags-console.php @@ -0,0 +1,6 @@ + [ + ...iterator_to_array($classifier->withAttribute(WorkflowInterface::class)->find()), + ], + 'temporal.activity' => [ + ...iterator_to_array($classifier->withAttribute(ActivityInterface::class)->find()), + ], +]; diff --git a/temporal/config/temporal/development-sql.yaml b/temporal/config/temporal/development-sql.yaml new file mode 100644 index 000000000..26c3b63c2 --- /dev/null +++ b/temporal/config/temporal/development-sql.yaml @@ -0,0 +1,6 @@ +limit.maxIDLength: + - value: 255 + constraints: {} +system.forceSearchAttributesCacheRefreshOnRead: + - value: true # Dev setup only. Please don't turn this on in production. + constraints: {} diff --git a/temporal/config/web/application.php b/temporal/config/web/application.php new file mode 100644 index 000000000..9fafee9ca --- /dev/null +++ b/temporal/config/web/application.php @@ -0,0 +1,23 @@ + [ + '__construct()' => [ + 'dispatcher' => DynamicReference::to(static function (Injector $injector) use ($params) { + return $injector->make(MiddlewareDispatcher::class) + ->withMiddlewares($params['middlewares']); + }), + 'fallbackHandler' => Reference::to(NotFoundHandler::class), + ], + ], +]; diff --git a/temporal/config/web/data-response.php b/temporal/config/web/data-response.php new file mode 100644 index 000000000..336029d79 --- /dev/null +++ b/temporal/config/web/data-response.php @@ -0,0 +1,28 @@ + ApiResponseFormatter::class, + DataResponseFactoryInterface::class => DataResponseFactory::class, + ContentNegotiator::class => [ + '__construct()' => [ + 'contentFormatters' => [ + 'text/html' => new HtmlDataResponseFormatter(), + 'application/xml' => new XmlDataResponseFormatter(), + 'application/json' => new JsonDataResponseFormatter(), + ], + ], + ], +]; diff --git a/temporal/config/web/error-handler.php b/temporal/config/web/error-handler.php new file mode 100644 index 000000000..d97bf3ecf --- /dev/null +++ b/temporal/config/web/error-handler.php @@ -0,0 +1,14 @@ + JsonRenderer::class, +]; diff --git a/temporal/config/web/middleware-dispatcher.php b/temporal/config/web/middleware-dispatcher.php new file mode 100644 index 000000000..616f54c4b --- /dev/null +++ b/temporal/config/web/middleware-dispatcher.php @@ -0,0 +1,14 @@ + MiddlewareFactory::class, +]; diff --git a/temporal/config/web/user.php b/temporal/config/web/user.php new file mode 100644 index 000000000..f41c0ab07 --- /dev/null +++ b/temporal/config/web/user.php @@ -0,0 +1,26 @@ + UserRepository::class, + IdentityWithTokenRepositoryInterface::class => UserRepository::class, + AuthenticationMethodInterface::class => HttpHeader::class, + Authentication::class => [ + 'class' => Authentication::class, + '__construct()' => [ + 'authenticationFailureHandler' => Reference::to(AuthRequestErrorHandler::class), + ], + ], +]; diff --git a/temporal/data/db/database.db b/temporal/data/db/database.db new file mode 100644 index 000000000..df1dad291 Binary files /dev/null and b/temporal/data/db/database.db differ diff --git a/temporal/data/nginx/default.conf b/temporal/data/nginx/default.conf new file mode 100644 index 000000000..4b4fefc2e --- /dev/null +++ b/temporal/data/nginx/default.conf @@ -0,0 +1,57 @@ +server { + listen 80; + + set $index_file "public/index.php"; + set $root "/app"; + + root $root; + index $index_file; + + error_log /var/log/nginx/error.log; #set + access_log /var/log/nginx/access.log; #set + + location ~* \.(js|css|png|html)$ { + root $root/public; + access_log off; + } + + location ~ [^/]\.php(/|$) { + fastcgi_pass yii-php:9000; + fastcgi_index $root/$index_file; + include fastcgi_params; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location / { + try_files $uri $uri/ /$index_file?$query_string; + } +} + + +server { + listen 81; + + set $index_file "public/index-test.php"; + set $root "/app"; + + root $root; + index $index_file; + + error_log /var/log/nginx/error.log; #set + access_log /var/log/nginx/access.log; #set + + location ~ [^/]\.php(/|$) { + fastcgi_pass yii-php:9000; + fastcgi_index $root/$index_file; + include fastcgi_params; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location / { + try_files $uri $uri/ /$index_file?$query_string; + } +} diff --git a/temporal/docker-compose.yml b/temporal/docker-compose.yml new file mode 100644 index 000000000..d48df67dc --- /dev/null +++ b/temporal/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3.5' + +services: + postgresql: + container_name: temporal-postgresql + image: postgres:9.6 + environment: + POSTGRES_PASSWORD: temporal + POSTGRES_USER: temporal + ports: + - 5433:5432 + + temporal: + container_name: temporal + image: temporalio/auto-setup:latest + depends_on: + - postgresql + environment: + - DB=postgresql + - DB_PORT=5432 + - POSTGRES_USER=temporal + - POSTGRES_PWD=temporal + - POSTGRES_SEEDS=postgresql + - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml + ports: + - 7233:7233 + volumes: + - ./config/temporal/:/etc/temporal/config/dynamicconfig + + temporal-admin-tools: + container_name: temporal-admin-tools + image: temporalio/admin-tools:latest + depends_on: + - temporal + environment: + - TEMPORAL_CLI_ADDRESS=temporal:7233 + stdin_open: true + tty: true + + temporal-web: + container_name: temporal-web + image: temporalio/web:latest + depends_on: + - temporal + environment: + - TEMPORAL_GRPC_ENDPOINT=temporal:7233 + - TEMPORAL_PERMIT_WRITE_API=true + ports: + - 8088:8088 + diff --git a/temporal/infection.json.dist b/temporal/infection.json.dist new file mode 100644 index 000000000..d319ab15a --- /dev/null +++ b/temporal/infection.json.dist @@ -0,0 +1,16 @@ +{ + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "php:\/\/stderr", + "badge": { + "branch": "master" + } + }, + "mutators": { + "@default": true + } +} diff --git a/temporal/phpunit.xml.dist b/temporal/phpunit.xml.dist new file mode 100644 index 000000000..b1d7346aa --- /dev/null +++ b/temporal/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./tests + + + + + + ./src + + + diff --git a/temporal/psalm.xml b/temporal/psalm.xml new file mode 100644 index 000000000..f8edfd3ae --- /dev/null +++ b/temporal/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/temporal/public/.htaccess b/temporal/public/.htaccess new file mode 100644 index 000000000..a82a91fb5 --- /dev/null +++ b/temporal/public/.htaccess @@ -0,0 +1,12 @@ +# use mod_rewrite for pretty URL support +RewriteEngine on + +# if $showScriptName is false in UrlManager, do not allow accessing URLs with script name +RewriteRule ^index.php/ - [L,R=404] + +# If a directory or a file exists, use the request directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# Otherwise forward the request to index.php +RewriteRule . index.php diff --git a/temporal/public/assets/.gitignore b/temporal/public/assets/.gitignore new file mode 100755 index 000000000..d6b7ef32c --- /dev/null +++ b/temporal/public/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/temporal/public/favicon.ico b/temporal/public/favicon.ico new file mode 100644 index 000000000..580ed732e Binary files /dev/null and b/temporal/public/favicon.ico differ diff --git a/temporal/public/index.php b/temporal/public/index.php new file mode 100644 index 000000000..5f9a907ee --- /dev/null +++ b/temporal/public/index.php @@ -0,0 +1,35 @@ +withEnabledTemporal(true); +$runner->run(); diff --git a/temporal/public/robots.txt b/temporal/public/robots.txt new file mode 100644 index 000000000..6f27bb66a --- /dev/null +++ b/temporal/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: \ No newline at end of file diff --git a/temporal/runtime/.gitignore b/temporal/runtime/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/temporal/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/temporal/src/Controller/HomeController.php b/temporal/src/Controller/HomeController.php new file mode 100644 index 000000000..46fc8c739 --- /dev/null +++ b/temporal/src/Controller/HomeController.php @@ -0,0 +1,52 @@ + 'World']; + $simpleActionUrl = $urlGenerator->generate('temporal/simple', $urlParameters); + $complicatedActionUrl = $urlGenerator->generate('temporal/complicated', $urlParameters); + $asynchronousActionUrl = $urlGenerator->generate('temporal/asynchronous', $urlParameters); + + $response = << + There are exist several examples how it works. +
+

Examples:

+ If you want to see how "simple" workflow works click here. +
+ There is usual call non-blocking action. It should work as faster as you can run usual php code. +
+ If you want to see how "complicated" workflow works click here. +
+ There are imitation for blocking action. Different methods calls will run with random delay: from 1 to 5 seconds per call. +
+ If you want to see how "deferred" workflow works click here. +
+ There are imitation for asynchronous action. You will get "job id" and you can track the status in another endpoint.
+ Logic for workflow will be same as "complicated" workflow. +
+ HTML; + + return $responseFactory->createResponse(200) + ->withBody( + $streamFactory->createStream($response) + ) + ->withHeader('Content-Type', 'text/html') + ; + } +} diff --git a/temporal/src/Controller/WorkflowController.php b/temporal/src/Controller/WorkflowController.php new file mode 100644 index 000000000..892812a16 --- /dev/null +++ b/temporal/src/Controller/WorkflowController.php @@ -0,0 +1,124 @@ +workflowClient = $workflowClient; + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function simpleAction(CurrentRoute $route): ResponseInterface + { + $name = (string)$route->getArgument('name'); + $start = microtime(true); + + $wf = $this->workflowClient->newWorkflowStub(FastWorkflow::class); + $result = $wf->run($name, 5); + + $end = microtime(true); + + $response = [ + 'microtime' => $end - $start, + 'result' => $result, + ]; + + return $this->response($response); + } + + public function complicatedAction(CurrentRoute $route): ResponseInterface + { + $name = (string)$route->getArgument('name'); + $start = microtime(true); + + $wf = $this->workflowClient->newWorkflowStub(LongWorkflow::class); + $result = $wf->run($name, 5); + + $end = microtime(true); + + $response = [ + 'microtime' => $end - $start, + 'result' => $result, + ]; + + return $this->response($response); + } + + public function asynchronousAction(UrlGeneratorInterface $urlGenerator, CurrentRoute $route): ResponseInterface + { + $name = (string)$route->getArgument('name'); + $start = microtime(true); + + $wf = $this->workflowClient->newWorkflowStub(LongWorkflow::class); + $process = $this->workflowClient->start($wf, $name, 10); + $id = $process->getExecution()->getID(); + + $end = microtime(true); + + $url = $urlGenerator->generate('temporal/asynchronous-status', ['id' => $id]); + + $delay = $end - $start; + $response = << + Job ID: {$id}
+ To see status of this job open click here. + HTML; + + return $this->response($response); + } + + public function asynchronousStatusAction(CurrentRoute $route) + { + $id = (string)$route->getArgument('id'); + $start = microtime(true); + + $wf = $this->workflowClient->newRunningWorkflowStub(LongWorkflow::class, $id); + $result = $wf->getStatus(); + + $end = microtime(true); + + $response = [ + 'Note' => 'Please update the page to see results', + 'microtime' => $end - $start, + 'result' => $result, + ]; + + return $this->response($response); + } + + private function response($data): ResponseInterface + { + $response = $this->responseFactory->createResponse(200); + if (is_string($data)) { + $content = $data; + $response = $response->withHeader('Content-Type', 'text/html'); + } else { + $content = json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); + } + + return $response->withBody( + $this->streamFactory->createStream($content) + ); + } +} diff --git a/temporal/src/Factory/ApiResponseDataFactory.php b/temporal/src/Factory/ApiResponseDataFactory.php new file mode 100644 index 000000000..96a4831fb --- /dev/null +++ b/temporal/src/Factory/ApiResponseDataFactory.php @@ -0,0 +1,49 @@ +getStatusCode() !== Status::OK) { + return $this->createErrorResponse() + ->setErrorCode($response->getStatusCode()) + ->setErrorMessage($this->getErrorMessage($response)); + } + + return $this->createSuccessResponse() + ->setData($response->getData()); + } + + public function createSuccessResponse(): ApiResponseData + { + return $this->createResponse()->setStatus('success'); + } + + public function createErrorResponse(): ApiResponseData + { + return $this->createResponse()->setStatus('failed'); + } + + public function createResponse(): ApiResponseData + { + return new ApiResponseData(); + } + + private function getErrorMessage(DataResponse $response): string + { + $data = $response->getData(); + if (is_string($data) && !empty($data)) { + return $data; + } + + return 'Unknown error'; + } +} diff --git a/temporal/src/Factory/RestGroupFactory.php b/temporal/src/Factory/RestGroupFactory.php new file mode 100644 index 000000000..1e12307ae --- /dev/null +++ b/temporal/src/Factory/RestGroupFactory.php @@ -0,0 +1,44 @@ + Method::GET, + 'list' => Method::GET, + 'post' => Method::POST, + 'put' => Method::PUT, + 'delete' => Method::DELETE, + 'patch' => Method::PATCH, + 'options' => Method::OPTIONS, + ]; + + public static function create(string $prefix, string $controller): Group + { + return Group::create($prefix)->routes(...self::createDefaultRoutes($controller)); + } + + private static function createDefaultRoutes(string $controller): array + { + $routes = []; + $reflection = new ReflectionClass($controller); + foreach (self::METHODS as $methodName => $httpMethod) { + if ($reflection->hasMethod($methodName)) { + $pattern = $methodName === 'list' ? '' : self::ENTITY_PATTERN; + $routes[] = Route::methods([$httpMethod], $pattern)->action([$controller, $methodName]); + } + } + + return $routes; + } +} diff --git a/temporal/src/Formatter/ApiResponseFormatter.php b/temporal/src/Formatter/ApiResponseFormatter.php new file mode 100644 index 000000000..5714cd1a7 --- /dev/null +++ b/temporal/src/Formatter/ApiResponseFormatter.php @@ -0,0 +1,34 @@ +apiResponseDataFactory = $apiResponseDataFactory; + $this->jsonDataResponseFormatter = $jsonDataResponseFormatter; + } + + public function format(DataResponse $dataResponse): ResponseInterface + { + $response = $dataResponse->withData( + $this->apiResponseDataFactory->createFromResponse($dataResponse)->toArray() + ); + + return $this->jsonDataResponseFormatter->format($response); + } +} diff --git a/temporal/src/Formatter/PaginatorFormatter.php b/temporal/src/Formatter/PaginatorFormatter.php new file mode 100644 index 000000000..84221d5c0 --- /dev/null +++ b/temporal/src/Formatter/PaginatorFormatter.php @@ -0,0 +1,28 @@ + $paginator->getPageSize(), + 'currentPage' => $paginator->getCurrentPage(), + 'totalPages' => $paginator->getTotalPages(), + ]; + } +} diff --git a/temporal/src/Installer.php b/temporal/src/Installer.php new file mode 100644 index 000000000..bb94661f1 --- /dev/null +++ b/temporal/src/Installer.php @@ -0,0 +1,34 @@ +dataResponseFactory = $dataResponseFactory; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + return $handler->handle($request); + } catch (ApplicationException $e) { + return $this->dataResponseFactory->createResponse($e->getMessage(), $e->getCode()); + } catch (RequestValidationException $e) { + return $this->dataResponseFactory->createResponse($e->getFirstError(), Status::BAD_REQUEST); + } + } +} diff --git a/temporal/src/NotFoundHandler.php b/temporal/src/NotFoundHandler.php new file mode 100644 index 000000000..955fe707c --- /dev/null +++ b/temporal/src/NotFoundHandler.php @@ -0,0 +1,34 @@ +responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->responseFactory->createResponse(404) + ->withBody( + $this->streamFactory->createStream('Not found') + ) + ->withHeader('Content-Type', 'text/html'); + } +} diff --git a/temporal/src/Temporal/Activity/CommonActivity.php b/temporal/src/Temporal/Activity/CommonActivity.php new file mode 100644 index 000000000..a5d116a68 --- /dev/null +++ b/temporal/src/Temporal/Activity/CommonActivity.php @@ -0,0 +1,27 @@ + 'Hello, ' . $name, + 'start' => $start, + 'end' => $end, + ]; + } +} diff --git a/temporal/src/Temporal/Workflow/FastWorkflow.php b/temporal/src/Temporal/Workflow/FastWorkflow.php new file mode 100644 index 000000000..61ce63b14 --- /dev/null +++ b/temporal/src/Temporal/Workflow/FastWorkflow.php @@ -0,0 +1,31 @@ +withStartToCloseTimeout(5) + ); + $promises = []; + + foreach (range(1, $count) as $item) { + $promises[] = $activity->fast($name); + } + + return yield Promise::all($promises); + } +} diff --git a/temporal/src/Temporal/Workflow/LongWorkflow.php b/temporal/src/Temporal/Workflow/LongWorkflow.php new file mode 100644 index 000000000..3d6083d18 --- /dev/null +++ b/temporal/src/Temporal/Workflow/LongWorkflow.php @@ -0,0 +1,70 @@ +end > 0 ? $this->end - $this->start : '---'; + $status = $this->status === 'done' + ? sprintf( + 'Processed %d tasks in %f seconds.', + count($this->done), + $time + ) + : $this->status; + + return [ + 'status' => $status, + 'total time' => $time, + 'done' => $this->done, + ]; + } + + #[\Temporal\Workflow\WorkflowMethod('long_workflow')] + public function run(string $name, int $count): \Generator + { + $activity = Workflow::newActivityStub( + CommonActivity::class, + ActivityOptions::new() + ->withStartToCloseTimeout(6) + ); + + $promises = []; + + foreach (range(1, $count) as $item) { + $promises[] = $activity->slow($name) + ->then( + function ($result) use ($item) { + $this->start = min($this->start, $result['start']); + $this->end = max($this->end, $result['end']); + $this->done['Task #' . $item] = $result; + return $result; + } + ); + } + + $this->status = 'processing'; + + $result = yield Promise::all($promises); + + $this->status = 'done'; + + return $result; + } +} diff --git a/temporal/tests/.gitkeep b/temporal/tests/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/temporal/tests/_data/database.db b/temporal/tests/_data/database.db new file mode 100644 index 000000000..82379435f Binary files /dev/null and b/temporal/tests/_data/database.db differ diff --git a/temporal/tests/_data/dump.sql b/temporal/tests/_data/dump.sql new file mode 100644 index 000000000..275aa60b1 --- /dev/null +++ b/temporal/tests/_data/dump.sql @@ -0,0 +1,58 @@ +create table user +( + id integer not null + primary key, + token text(128) not null, + login text(48) not null, + password_hash text(255) not null, + created_at datetime not null, + updated_at datetime not null +); + +create unique index user_index_login_5f1de4d7ae5e4 + on user (login); + +create unique index user_index_token_5f1de4d7ae5f9 + on user (token); + +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (1, 'lev1ZsWCzqrMlXRI2sT8h4ApYpSgBMl1xf6D4bCRtiKtDqw6JN36yLznargilQ_rEJz9zTfcUxm53PLODCToF9gGin38Rd4NkhQPOVeH5VvZvBaQlUg64E6icNCubiAv', 'Opal1144', '$2y$13$IkvO58xZv3lJkGUpwTGPg.6NQj1mrSMOUIi3G4sBX6AoShRXTISES', '2020-07-26 20:18:11', '2020-08-03 19:10:48'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (2, 'yU0asLbo5th60rRL4L0EGVRl0FSPHL7gCIZUs1IQi5YfdOwwbsuz1ZIA0cK2au22Gkk--XpSCsLxqE2JD_E-VxMzyuaW8pLBSxjyP0bmboWyuyNuUj1kGyGqM0v0C0fE', 'Haven7127', '$2y$13$/aVlgRSYPWUjHOlSs1PSuOkmOVB7HdQLc6UmqJLvSMbNUSy36zwqi', '2020-07-26 20:18:16', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (3, 'AzGRKjE4OvZhNUYxpnGqUnqxBejXn87MsPw2RInEGkMXSM4px_-pfCSntPQpCQSxAREh-uJcHYovMGsqP0kjM6aTeQHzEABxc_FOVt50kClftikYretlhJ0uOJBrQghS', 'Athena7928', '$2y$13$kXheeNXBm5fj2I/0hNrv2euOkSkPhXZeQwna7/eTLnktdUonPwtKK', '2020-07-26 20:18:11', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (4, 'P0ZcZ117Jh2W0xaAeq1YMoR5itemK04vZWTsqliKDbxIm-Hxtptt_cvwng5Oiy8oeMvRwB7eUSIszMTfcU6UtOZZ7WcuEqICpKdaXiY3uEOUibLO48oLZ5XXV9HREQxE', 'Shayne6889', '$2y$13$ri9geQr79ytcRcZ9jO7F6OSN.1MeNJb6hfsM2Iysoltjluds5YmMW', '2020-07-26 20:18:14', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (5, 'gUEH-ztBM4-0boeX10NS_QHsQWHP4O1_Qr7CeoUU78gadFpYE8GqhudgaUklmihARs46GnYPKCWAPqgEqdXjUx9TVcwP0J0KiQEFC2oJrtsGRLziPSZg3HryA2eC1MRi', 'Wilburn1086', '$2y$13$NcMhRseiDyDMm3yhF8/45OkavtVxtIix8tMJzYI5xHx3dYjRvzS3S', '2020-07-26 20:18:13', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (6, '9OA09AZt8sYA7Ehi_ttnLbLwsMGLPghtQu260JrRwrBJaMTabuBQdIEHbJNW0BcAHF2yDm5QoEQEY5MCVudkre4yDWB6EddYSei9mqn_3uwQbfhY2jz1S0cENapTLIrF', 'Rossie5155', '$2y$13$9oWNAP9IXBIW3KY9M.KxyecgFMEdot/k8jGNdIkptR9Bf0Oo42oxG', '2020-07-26 20:18:12', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (7, '5SQSC0l70-3eerJKNlNOz7BTAl3PuylMy5KldV2jRR4Cq2dmT5dM33tyyb5u2IwW_GKVWL9dL5eXgGk9U14KH9lxQSz8_wwStwoTz7KrDeOj3Oae-k87Nakj5QhSuAEU', 'Niko6385', '$2y$13$bnDvR9OiqmAulLYBYNMjuO4IUOl1TwqfQy1jnMwN1ESB8J.i/Si.S', '2020-07-26 20:18:15', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (8, 'FMJIgz8TvmroJ28uAat0jyeiYADlWfpNpoR9kRBFGmVsUHAw_u1kJ0-O8EHB5UfJaqLNhrskCAf26kZCXuB747DaPpBDHRd5mLZ5b7Tq5TxaK659Glfu-gZzC0Pw_u2d', 'Cordie5190', '$2y$13$ah.45kfteHCwwLQZAd9AWeuu5pOvhfJFU8x8TKA7rq/Y.ty8cEAlO', '2020-07-26 20:18:13', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (9, '_PGRAIc8eKL2IQhmsMImrh49w43Y2byQsj0nUHul3Q4HFectkmd8nhh4ORTcvvYxNfx0Vbx4jtG6-Oph8lxRnw0f7K-qOTqgLMRdv5TqlVyZO74dNz4NynTd9pYTiQDi', 'Leola1793', '$2y$13$fatVjj.ySzcur1F6Ks2/vOlGsQYh8dkIg6gQS8WjkBGRHwgZg69ra', '2020-07-26 20:18:14', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (10, 'm6hicvzj0r5vXmpGo5Ck97-Os38g16Qi8JQDBjJTbEehrcioZOQf9GL7410CvMy1mEhIbOMFlzdWERxpJ-XMvTXB9oF3fPrjA3UB7v5aNfgP9sGeWizfb05uaA8eaeQm', 'Rachael2982', '$2y$13$zgnmnzkFT7xkgYQj4JZiFerX6NyNMlwbBLWKnbxV/bXatO5yx7XM.', '2020-07-26 20:18:12', '2020-07-26 20:18:17'); +INSERT INTO user (id, token, login, password_hash, created_at, updated_at) VALUES (11, 'ooTRTrvO925xdpKBR8adhysr-KSKRsZK4JG_hLfghxXKQ2z5TLdmjJK8rc8Qn4Iayotg6GJF1BoA0AHeL9YuTl-qnlkmlC6F3NEgomipGQYw7odI-D9p_VAFSj1j2dIS', 'Lizzie7529', '$2y$13$tzg5/FlklhOk.e9opCisfe5aOuKREVtnyy.6IeUL.hva1CjAsl3NW', '2020-07-26 20:18:15', '2020-07-26 20:18:17'); + +create table post +( + id integer not null + primary key, + slug text(128) not null, + title text(255) default '' not null, + status integer default 0 not null, + content text not null, + created_at datetime not null, + updated_at datetime not null, + user_id integer not null + references user + on update cascade on delete cascade +); + +create index post_index_user_id_5f1de4d7a5678 + on post (user_id); + +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (1, 'xkHilRENIavP1Ht6WS7UHKGBv__NDYlrmpvx0GieP91AnQnnRMUKK9OG8so0x38mColZj64UBF6UFrMTvYCgWUIQx0I74Vgcoy63PNCqwVz5hYYi0DYqn3xAuXIKgnbT', 'Quia non natus aut qui saepe ut non.', 0, 'Mock Turtle in the distance, screaming with passion. She had quite forgotten the little door: but, alas! either the locks were too large, or the key was too small, but at any rate, the Dormouse into the air off all its feet at once, while all the rats and--oh dear!'' cried Alice, with a lobster as a cushion, resting their elbows on it, (''which certainly was not a VERY unpleasant state of mind, she turned the corner, but the Gryphon replied very gravely. ''What else have you executed.'' The miserable Hatter dropped his teacup instead of onions.'' Seven flung down his brush, and had to fall a long sleep you''ve had!'' ''Oh, I''ve had such a thing before, and he went on to himself as he spoke. ''A cat may look at it!'' This speech caused a remarkable sensation among the branches, and every now and then; such as, ''Sure, I don''t take this child away with me,'' thought Alice, ''it''ll never do to come yet, please your Majesty!'' the soldiers had to stoop to save her neck from being broken. She hastily put down yet, before the trial''s over!'' thought Alice. ''I''m glad they''ve begun asking riddles.--I believe I can say.'' This was quite a long tail, certainly,'' said Alice, timidly; ''some of the Mock Turtle. ''Certainly not!'' said Alice to herself, ''after such a nice soft thing to get in?'' ''There might be hungry, in which you usually see Shakespeare, in the world she was saying, and the constant heavy sobbing of the door began sneezing all at once. ''Give your evidence,'' the King in a piteous tone. And the moral of that is--"Be what you mean,'' said Alice. ''Anything you like,'' said the Hatter: ''as the things between whiles.'' ''Then you should say what you had been jumping about like that!'' But she waited patiently. ''Once,'' said the Cat. ''I''d nearly forgotten that I''ve got to see how he did not like to drop the jar for fear of killing somebody, so managed to swallow a morsel of the Gryphon, sighing in his sleep, ''that "I breathe when I get it home?'' when it had gone. ''Well! I''ve often seen a good deal frightened at the cook, to see that queer little toss of her voice. Nobody moved. ''Who cares for fish, Game, or any other dish? Who would not join the dance? Will you, won''t you join the dance? Will you, won''t you join the dance?"'' ''Thank you, sir, for your walk!" "Coming in a few yards off. The Cat only grinned a little snappishly. ''You''re enough to try the experiment?'' ''HE might bite,'' Alice cautiously replied: ''but I haven''t been invited yet.'' ''You''ll see me there,'' said the Gryphon. ''I''ve forgotten the Duchess said in a sulky tone, as it could go, and broke to pieces against one of them at dinn--'' she checked herself hastily. ''I don''t quite understand you,'' she said, by way of settling all difficulties, great or small. ''Off with her head!'' the Queen till she had put on one knee as he wore his crown.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 2); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (2, 'Soc7hNmD1luvRqJb1xcqgEAwLDOzSeO7qN0hdQ0tMOBZUjAwEkH492JWzS8beEfCndtiluat2VUhKOeT6aMO6v3u6BzNjWLvmXXZxLCt44uXfz-mZ2sm2Pg-1A0uGvgO', 'Dolor commodi cumque illum et.', 1, 'I''m a hatter.'' Here the Queen in front of them, with her arms round it as you go on? It''s by far the most interesting, and perhaps as this before, never! And I declare it''s too bad, that it is!'' As she said to Alice; and Alice was very uncomfortable, and, as there was not quite like the three were all ornamented with hearts. Next came the royal children, and make out which were the verses on his knee, and the words ''DRINK ME'' beautifully printed on it were white, but there was silence for some while in silence. At last the Gryphon repeated impatiently: ''it begins "I passed by his face only, she would get up and repeat something now. Tell her to begin.'' For, you see, because some of them attempted to explain the paper. ''If there''s no use denying it. I suppose Dinah''ll be sending me on messages next!'' And she began shrinking directly. As soon as she could, ''If you can''t take LESS,'' said the Mouse. ''--I proceed. "Edwin and Morcar, the earls of Mercia and Northumbria, declared for him: and even Stigand, the patriotic archbishop of Canterbury, found it made.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 3); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (3, 'SS1LB4tH5utAgI8da3jvBx7JLEL6EhbC8JYDvNiWfKNsAds4AYZmGXIqmHRjcwXNbQOIFwk0YaoGTWoAYWKcHW6biJ8wRedGtj6ySO3mO1kFoUt1VNhSrsA_FgMx8B8R', 'Non nobis repudiandae eius non. Est veniam rerum officiis.', 0, 'Dormouse go on crying in this affair, He trusts to you to offer it,'' said the Mock Turtle went on. ''We had the best cat in the night? Let me see: four times seven is--oh dear! I shall be a letter, after all: it''s a very decided tone: ''tell her something worth hearing. For some minutes the whole party look so grave and anxious.) Alice could bear: she got to go among mad people,'' Alice remarked. ''Oh, you can''t think! And oh, my poor hands, how is it twelve? I--'' ''Oh, don''t bother ME,'' said Alice aloud, addressing nobody in particular. ''She''d soon fetch it back!'' ''And who are THESE?'' said the Rabbit actually TOOK A WATCH OUT OF ITS WAISTCOAT-POCKET, and looked very uncomfortable. The moment Alice appeared, she was now about a foot high: then she heard a little girl,'' said Alice, and she felt that she ran out of sight: ''but it doesn''t mind.'' The table was a little pattering of footsteps in the distance, screaming with passion. She had not long to doubt, for the first verse,'' said the Mouse, getting up and straightening itself out again, and the other players, and shouting ''Off with her head! Off--'' ''Nonsense!'' said Alice, as she was not a mile high,'' said Alice. ''Call it what you mean,'' the March Hare interrupted in a hurry: a large piece out of sight before the trial''s begun.'' ''They''re putting down their names,'' the Gryphon hastily. ''Go on with the bread-and-butter getting so used to it!'' pleaded poor Alice. ''But you''re so easily offended!'' ''You''ll get used to do:-- ''How doth the little--"'' and she could remember them, all these strange Adventures of hers would, in the other. ''I beg pardon, your Majesty,'' the Hatter began, in a whisper.) ''That would be four thousand miles down, I think--'' (she was obliged to say than his first remark, ''It was the Hatter. ''It isn''t mine,'' said the youth, ''and your jaws are too weak For anything tougher than suet; Yet you turned a back-somersault in at once.'' However, she got back to finish his story. CHAPTER IV. The Rabbit Sends in a tone of great surprise. ''Of course you know what they''re about!'' ''Read them,'' said the Hatter. ''You MUST remember,'' remarked the King, with an anxious look at the door-- Pray, what is the same size for going through the glass, and she grew no larger: still it had some kind of sob, ''I''ve tried the little glass box that was linked into hers began to repeat it, but her head pressing against the roof off.'' After a time there were three gardeners instantly jumped up, and began whistling. ''Oh, there''s no use in crying like that!'' But she went on. ''Or would you like the Mock Turtle. ''She can''t explain MYSELF, I''m afraid, but you might catch a bad cold if she did so, very carefully, with one of the trees behind him. ''--or next day, maybe,'' the Footman continued in the sea. But they HAVE their tails in their proper places--ALL,'' he repeated with great curiosity, and this Alice thought to herself. (Alice had been to the executioner: ''fetch her here.'' And the moral of that dark hall, and wander about among those beds of bright flowers and the other two were using.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 5); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (4, 'MiJcIMhnKGsT8_JK9IQmmp_Z7iFnGWaiVl2dr2I3Zj8JOKh9AgVKpZoyLq3_B9vf1DvtG0lhLNSsF03t_MePM6YlbS1dlyZZTYWBjzfmY4FqT0Dh7O5HrPCokooVVVQe', 'Non dolor eos facere et.', 0, 'WAS a curious dream, dear, certainly: but now run in to your little boy, And beat him when he pleases!'' CHORUS. ''Wow! wow! wow!'' ''Here! you may stand down,'' continued the Gryphon. ''I mean, what makes them sour--and camomile that makes people hot-tempered,'' she went on to himself in an undertone to the door, and tried to fancy to herself ''Now I can go back by railway,'' she said to the shore. CHAPTER III. A Caucus-Race and a large mustard-mine near here. And the muscular strength, which it gave to my jaw, Has lasted the rest of the Lobster Quadrille?'' the Gryphon in an offended tone. And she began fancying the sort of lullaby to it as you liked.'' ''Is that all?'' said the Caterpillar decidedly, and he called the Queen, tossing her head on her face in her life before, and she hurried out of sight: then it chuckled. ''What fun!'' said the Gryphon. ''Well, I can''t put it in a low, timid voice, ''If you didn''t sign it,'' said Alice thoughtfully: ''but then--I shouldn''t be hungry for it, while the Mock Turtle said with a bound into the earth. At last the Mouse, who was reading the list of singers. ''You may not have lived much under the circumstances. There was a long silence after this, and after a few minutes that she wasn''t a bit of mushroom, and crawled away in the.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 6); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (5, 'YcVobhI00iiZnD8YB1_4FdYssSogZGk3Kxo8_haJnuqFwiuKit1NE4p_P4COqwP4Bj2KEA2RAvJzrtvDZUYxUJ6o40TFuiIpRHzEtKUCpnCpqoUXurDOkblCIq6GyLiv', 'Vero dolorem numquam dolorum non eaque laboriosam ducimus.', 0, 'Duchess, the Duchess! Oh! won''t she be savage if I''ve been changed for any of them. However, on the hearth and grinning from ear to ear. ''Please would you tell me,'' said Alice, and she grew no larger: still it was a dispute going on between the executioner, the King, ''or I''ll have you executed on the trumpet, and then the other, saying, in a low curtain she had never been so much contradicted in her face, and large eyes full of the what?'' said the Hatter, ''you wouldn''t talk about trouble!'' said the Mock Turtle sighed deeply, and began, in a voice she had hoped) a fan and gloves. ''How queer it seems,'' Alice said with some difficulty, as it was very hot, she kept on puzzling about it while the Mouse heard this, it turned a corner, ''Oh my ears and the words a little, ''From the Queen. ''Well, I should think it so VERY nearly at the thought that she ran across the garden, called out in a languid, sleepy voice. ''Who are YOU?'' Which brought them back again to the jury, who instantly made a dreadfully ugly child: but it said nothing. ''When we were little,'' the Mock Turtle sighed deeply, and began, in a twinkling! Half-past one, time for dinner!'' (''I only wish people knew that: then they both bowed low, and their slates and pencils had been looking at them with one eye; ''I seem to see it quite plainly through the little golden key, and Alice''s elbow was pressed so closely against her foot, that there was no time she''d have everybody executed, all round. (It was this last remark, ''it''s a vegetable. It doesn''t look like it?'' he said, turning to Alice, very much what would happen next. The first thing I''ve got to come once a week: HE taught us Drawling, Stretching, and Fainting in Coils.'' ''What was THAT like?'' said Alice. ''Come, let''s hear some of the tail, and ending with the end of the trees upon her arm, with its tongue hanging out of the cupboards as she spoke--fancy CURTSEYING as you''re falling through the little golden key, and Alice''s first thought was that it seemed quite natural); but when the White Rabbit, who was sitting on the other side. The further off from England the nearer is to France-- Then turn not pale, beloved snail, but come and join the dance?"'' ''Thank you, sir, for your walk!" "Coming in a loud, indignant voice, but she did not sneeze, were the two creatures, who had been all the rest, Between yourself and me.'' ''That''s the reason is--'' here the conversation a little. ''''Tis so,'' said the Footman, and began staring at the Queen, who was reading the list of singers. ''You may not have lived much under the sea--'' (''I haven''t,'' said Alice)--''and perhaps you haven''t found it so VERY remarkable in that; nor did Alice think it so VERY tired of sitting by her sister sat still just as if she had accidentally upset the week before. ''Oh, I know!'' exclaimed Alice, who had been looking over their shoulders, that all the things being alive; for instance, there''s the arch I''ve got back to the porpoise, "Keep back, please: we don''t want YOU with us!"'' ''They were learning to draw, you know--'' She had quite a conversation of it now in sight, hurrying down it. There could be beheaded, and that makes them sour--and camomile that makes people hot-tempered,'' she went out, but it had VERY long claws and a pair of boots every Christmas.'' And she squeezed.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 8); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (6, 'LnYwm21c4P2qx_TjctMKDoUJVZdTaCEAC6ZUAJQNeJta_T_Q7rPtcT3mdGXBxOEPNl94ZoURHoQb4tmqXH3nA8xo_s9WgnEH4gzD25A_E0DLsoTExXtTj7IiLELtTd58', 'Recusandae autem quam maiores delectus esse eum eaque et.', 0, 'I''m sure I can''t quite follow it as a cushion, resting their elbows on it, and very soon had to sing this:-- ''Beautiful Soup, so rich and green, Waiting in a confused way, ''Prizes! Prizes!'' Alice had not gone (We know it was sneezing and howling alternately without a cat! It''s the most curious thing I ask! It''s always six o''clock now.'' A bright idea came into her eyes; and once again the tiny hands were clasped upon her knee, and looking at Alice the moment she quite forgot how to set about it; and while she was now more than Alice could hardly hear the rattle of the bread-and-butter. Just at this moment Alice appeared, she was considering in her French lesson-book. The Mouse only growled in reply. ''That''s right!'' shouted the Queen. ''You make me smaller, I suppose.'' So she set to work very carefully, nibbling first at one end to the fifth bend, I think?'' he said to Alice, and sighing. ''It IS a Caucus-race?'' said Alice; not that she looked down at her hands, wondering if anything would EVER happen in a trembling voice to its feet, ran round the refreshments!'' But there seemed to quiver all over with William the Conqueror.'' (For, with all their simple joys, remembering her own mind (as well as she wandered about in all my life, never!'' They had not long to doubt, for the Dormouse,'' thought Alice; ''but when you throw them, and he called the Queen, who had spoken first. ''That''s none of YOUR adventures.'' ''I could tell you my adventures--beginning from this side of WHAT?'' thought Alice; ''but when you throw them, and all sorts of things--I can''t remember things as I was thinking I should think you can find them.'' As she said this she looked down into its mouth open, gazing up into the jury-box, or they would call after her: the last few minutes she heard the Rabbit came up to her lips. ''I know what to uglify is, you know. Come on!'' So they went up to her ear, and whispered ''She''s under sentence of execution.'' ''What for?'' said Alice. ''Come, let''s hear some of them with the lobsters to the other, and making faces at him as he spoke. ''A cat may look at all like the wind, and the shrill voice of the cattle in the air. Even the Duchess was VERY ugly; and secondly, because they''re making such VERY short remarks, and she felt that she was a very long silence, broken only by an occasional exclamation of ''Hjckrrh!'' from the sky! Ugh, Serpent!'' ''But I''m NOT a serpent!'' said Alice very meekly: ''I''m growing.'' ''You''ve no right to grow up again! Let me see: I''ll give them a new idea to Alice, they all crowded round her, about the reason and all must have got in as well,'' the Hatter went on, looking anxiously round to see it.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 4); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (7, '5jfQZ3gqLvu0Q-sJqXTGcHW5niY7nQnOesRE73xJMRmgZSCEWJXQSFayuMwBBC3v9QZ4cUqqZETJWgBQiS91isAg1MMIeRTgz06LE_ek9AJ3F2VHg_oQDrWAeZFZAtSD', 'Praesentium eos molestiae saepe ad dolore omnis sunt.', 1, 'I''m not Ada,'' she said, ''and see whether it''s marked "poison" or not''; for she felt that it felt quite unhappy at the sudden change, but she thought it over afterwards, it occurred to her feet, for it to annoy, Because he knows it teases.'' CHORUS. (In which the words ''DRINK ME,'' but nevertheless she uncorked it and put it right; ''not that it signifies much,'' she said to Alice, very earnestly. ''I''ve had nothing yet,'' Alice replied in a sulky tone; ''Seven jogged my elbow.'' On which Seven looked up eagerly, half hoping that they couldn''t get them out of that is, but I shall only look up and say "How doth the little passage: and THEN--she found herself falling down a very curious to know your history, she do.'' ''I''ll tell it her,'' said the Cat, ''if you don''t like it, yer honour, at all, as the question was evidently meant for her. ''Yes!'' shouted Alice. ''Come on, then!'' roared the Queen, and in a deep, hollow tone: ''sit down, both of you, and listen to me! When I used to it in her brother''s Latin Grammar, ''A mouse--of a mouse--to a mouse--a mouse--O mouse!'') The Mouse only shook its head impatiently, and said, very gravely, ''I think.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 1); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (8, 'yEU6e4GR7j_qK71471NK-BF2VwqjKT_9y4ZHiKWmhUSazLykv_r_cCTvlPn3FJfrsXOOW91vwLpuLuFnq-PbpXYkZiLh7TjJecEVq643kDqGlgQMcGhNpEGerQws1q7O', 'Aut qui fugiat consequuntur.', 1, 'Alice, rather doubtfully, as she swam nearer to watch them, and just as well wait, as she ran; but the Mouse in the sea. The master was an immense length of neck, which seemed to have no idea what to do, and perhaps after all it might injure the brain; But, now that I''m doubtful about the reason of that?'' ''In my youth,'' said his father, ''I took to the Mock Turtle, ''Drive on, old fellow! Don''t be all day to day.'' This was not a bit hurt, and she crossed her hands up to her that she was shrinking rapidly; so she went to the shore, and then keep tight hold of its mouth, and addressed her in the distance would take the hint; but the Mouse replied rather impatiently: ''any shrimp could have been ill.'' ''So they were,'' said the King replied. Here the other ladder?--Why, I hadn''t drunk quite so much!'' Alas! it was impossible to say to itself, ''Oh dear! Oh dear! I wish you could manage it?) ''And what an ignorant little girl she''ll think me for a long breath, and said nothing. ''When we were little,'' the Mock Turtle would be so kind,'' Alice replied, so eagerly that the Mouse with an air of great surprise. ''Of course not,'' Alice cautiously replied, not feeling at all fairly,'' Alice began, in a trembling voice, ''Let us get to twenty at that rate! However, the Multiplication Table doesn''t signify: let''s try the first question, you know.'' He was looking.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 3); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (9, 'CAUKDjr3OndxkpMi16SehwAb6inVNrm0SF6VGF1iCzPpCKlm-5ZhPHBSL8mIQP9zVYs6UcmWWglHg0bXjT4otl1q0jXxK9oJomGJ7YZmwVGuqk3RCJFIPwhUC6LC3uo-', 'Rerum enim itaque enim fugiat inventore.', 1, 'The Mouse looked at it uneasily, shaking it every now and then; such as, ''Sure, I don''t keep the same height as herself; and when she had nothing yet,'' Alice replied eagerly, for she was saying, and the little golden key and hurried off to the other: the Duchess to play croquet.'' The Frog-Footman repeated, in the lap of her ever getting out of sight before the officer could get to the jury, in a low voice, ''Your Majesty must cross-examine the next witness.'' And he added looking angrily at the Queen, turning purple. ''I won''t!'' said Alice. ''Come on, then,'' said Alice, feeling very glad to do that,'' said Alice. ''I''ve read that in some alarm. This time there were no arches left, and all sorts of little Alice and all must have been that,'' said Alice. ''Well, then,'' the Gryphon replied very solemnly. Alice was too slippery; and when she went back to the Knave was standing before them, in chains, with a knife, it usually bleeds; and she jumped up and beg for its dinner, and all the things I used to it in a melancholy air, and, after glaring at her side. She was moving them about as she added, to herself, as usual. ''Come, there''s no use going back to finish his story. CHAPTER IV. The Rabbit Sends in a hurry. ''No, I''ll look first,'' she said, by way of settling all difficulties, great or small. ''Off with their heads!'' and the other side, the puppy made another rush at the Gryphon went on, ''I must be Mabel after all, and I never understood what it might not escape again, and made a snatch in the middle, wondering how she would have called him a fish)--and rapped loudly at the thought that it was YOUR table,'' said Alice; ''it''s laid for a minute or two sobs choked his voice. ''Same as if she was quite surprised to find her way through the little glass table. ''Now, I''ll manage better this time,'' she said, as politely as she fell very slowly, for she was coming to, but it just grazed his nose, you know?'' ''It''s the thing yourself, some winter day, I will prosecute YOU.--Come, I''ll take no denial; We must have been ill.'' ''So they were,'' said the March Hare went on. Her listeners were perfectly quiet till she got to do,'' said the Cat: ''we''re all mad here. I''m mad. You''re mad.'' ''How do you want to stay in here any longer!'' She waited for some minutes. The Caterpillar and Alice looked all round her at the righthand bit again, and did not venture to ask them what the flame of a book,'' thought Alice to herself, ''to be going messages for a minute, trying to explain it is to give the hedgehog to, and, as the other.'' As soon as there seemed to be sure! However, everything is to-day! And yesterday things went on at last, more calmly, though still sobbing a little timidly, for she felt a little house in it about four inches deep and reaching half down the chimney, has he?'' said Alice to herself. (Alice had been to her, ''if we had the dish as its share of the earth. Let me see: four times five is twelve, and four times six is.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 7); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (10, '4AdnoiqLp20kNTlT50ZDY4rpjT6fLiEaTfRwQBg0O2SIgplhZwZGJrTdwzXJ7hUt1bhm4j4U4UsFkJRDSNskKthEi-B6zJzSGJ6YCCwsufLlh_ETNsayfcnT-oMT9g8G', 'Aut vel aut molestias ut eos.', 0, 'Majesty,'' said Two, in a melancholy air, and, after folding his arms and frowning at the March Hare. ''Exactly so,'' said Alice. ''I''m glad I''ve seen that done,'' thought Alice. ''I''ve tried every way, and then quietly marched off after the others. ''We must burn the house if it makes me grow large again, for really I''m quite tired of sitting by her sister kissed her, and said, very gravely, ''I think, you ought to be no use in waiting by the Queen merely remarking that a red-hot poker will burn you if you were never even spoke to Time!'' ''Perhaps not,'' Alice cautiously replied, not feeling at all fairly,'' Alice began, in a moment. ''Let''s go on in the sky. Twinkle, twinkle--"'' Here the Queen said severely ''Who is it twelve? I--'' ''Oh, don''t talk about trouble!'' said the Mouse. ''Of course,'' the Gryphon went on. Her listeners were perfectly quiet till she shook the house, and wondering whether she could not think of anything to put it in a long, low hall, which was immediately suppressed by the end of the jurors had a consultation about this, and Alice joined the procession, wondering very much to-night, I should say "With what porpoise?"'' ''Don''t you mean that you couldn''t cut off a little timidly: ''but it''s no use in waiting by the end of the Lobster Quadrille, that she was shrinking rapidly; so she went on again: ''Twenty-four hours, I THINK; or is it I can''t see you?'' She was close behind us, and he''s treading on my tail. See how eagerly the lobsters and the sound of a tree. ''Did you say it.'' ''That''s nothing to what I eat" is the same year for such a nice soft thing to nurse--and she''s such a rule at processions; ''and besides, what would happen next. ''It''s--it''s a very good height indeed!'' said the King: ''however, it may kiss my hand if it had fallen into it: there was nothing so VERY wide, but she was shrinking rapidly; so she turned to the part about her pet: ''Dinah''s our cat. And she''s such a hurry to get in at all?'' said the Queen, but she remembered having seen such a dear quiet thing,'' Alice went on, spreading out the verses the White Rabbit put on his spectacles. ''Where shall I begin, please your Majesty,'' said Two, in a frightened tone. ''The Queen of Hearts, he stole those tarts, And took them quite away!'' ''Consider your verdict,'' the King said, turning to the jury, of course--"I GAVE HER ONE, THEY GAVE HIM TWO--" why, that must be a walrus or hippopotamus, but then she had this fit) An obstacle that came between Him, and ourselves, and it. Don''t let me hear the words:-- ''I speak severely to my jaw, Has lasted the rest were quite silent, and looked anxiously at the March Hare meekly replied. ''Yes, but I can''t put it in time,'' said the Mock Turtle went on. ''Or would you like the Queen?'' said the Caterpillar sternly. ''Explain yourself!'' ''I can''t explain MYSELF, I''m afraid, sir'' said Alice, ''how am I to do such a tiny golden key, and unlocking the door between us. For instance, suppose it were white, but.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 7); +INSERT INTO post (id, slug, title, status, content, created_at, updated_at, user_id) VALUES (11, 'LIkxj50Kp4PE4vlRW2RyayTKGTmJNsFdckr9TYeVDBAMjTSSXiqITlQudv7Eozhy9vUBzwgU2FuXSdCv_bagBOoGlu_Xa3myOIFn7tmbs2HRzKNl4Nv5T2vuIFjjTH9-', 'Eveniet est nam sapiente odit architecto et.', 1, 'Alice replied thoughtfully. ''They have their tails in their paws. ''And how do you know the song, perhaps?'' ''I''ve heard something like this:-- ''Fury said to Alice. ''Nothing,'' said Alice. ''I''ve so often read in the distance. ''Come on!'' cried the Gryphon, and the second verse of the shelves as she went nearer to make it stop. ''Well, I''d hardly finished the guinea-pigs!'' thought Alice. ''I wonder what you''re at!" You know the meaning of half an hour or so, and giving it a violent shake at the window, and some of them even when they passed too close, and waving their forepaws to mark the time, while the rest of the court. (As that is rather a handsome pig, I think.'' And she squeezed herself up on tiptoe, and peeped over the fire, and at once to eat some of the fact. ''I keep them to sell,'' the Hatter began, in a very truthful child; ''but little girls in my kitchen AT ALL. Soup does very well without--Maybe it''s always pepper that had a bone in his sleep, ''that "I breathe when I get SOMEWHERE,'' Alice added as an explanation; ''I''ve none of YOUR business, Two!'' said Seven. ''Yes, it IS his business!'' said Five, ''and I''ll tell him--it was for bringing the cook was leaning over the wig, (look at the Queen, the royal children; there were any tears. No, there were TWO little shrieks, and more faintly came, carried on the top of her going, though she felt a violent blow underneath her chin: it had a little snappishly. ''You''re enough to drive one crazy!'' The Footman seemed to have no answers.'' ''If you didn''t like cats.'' ''Not like cats!'' cried the Mouse, who was a large canvas bag, which tied up at the bottom of a tree a few minutes it puffed away without being seen, when she got to see a little glass table. ''Now, I''ll manage better this time,'' she said to herself; ''I should like to be no chance of getting her hands on her lap as if nothing had happened. ''How am I to get out of sight, he said in a court of justice before, but she thought it must be on the twelfth?'' Alice went on, looking anxiously about her. ''Oh, do let me hear the words:-- ''I speak severely to my right size to do it?'' ''In my youth,'' said the Hatter added as an explanation. ''Oh, you''re sure to do anything but sit with its wings. ''Serpent!'' screamed the Gryphon. ''Then, you know,'' said the Pigeon; ''but I must be collected at once and put back into the garden door. Poor Alice! It was so full of tears, but said nothing. ''This here young lady,'' said the Cat. ''I said pig,'' replied Alice; ''and I wish I could not be denied, so she began nursing her child again, singing a sort of people live about here?'' ''In THAT direction,'' the Cat said, waving its tail when it''s pleased. Now I growl when I''m pleased, and wag my tail when I''m pleased, and wag my tail when it''s angry, and wags its tail about in all their simple joys, remembering her own mind (as well as she went on, looking anxiously about her. ''Oh, do let me hear the rattle of the shepherd boy--and the sneeze of the ground, Alice soon began talking again. ''Dinah''ll miss me very much what would happen next. First, she tried to curtsey as she could not even get her head impatiently; and, turning to Alice, and looking anxiously round to see if there are, nobody attends to them--and you''ve no idea what Latitude or Longitude I''ve got to?'' (Alice had no reason to be Number One,'' said Alice. ''Why?'' ''IT DOES THE BOOTS AND SHOES.'' the Gryphon went on again:-- ''You may go,'' said the Hatter: ''but you could only hear whispers now and then, if I chose,'' the Duchess by this time, and was going off into a sort of a well?'' ''Take some more of the table, but there was a very small cake, on which the cook was busily stirring the soup, and seemed to Alice severely. ''What are they made of?'' Alice asked in a great thistle, to keep herself from being run over; and the March Hare will be much the same thing as "I get what I like"!'' ''You might.', '2020-07-26 20:18:16', '2020-07-26 20:18:17', 4); \ No newline at end of file diff --git a/temporal/tests/_output/.gitignore b/temporal/tests/_output/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/temporal/tests/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/temporal/tests/_support/AcceptanceTester.php b/temporal/tests/_support/AcceptanceTester.php new file mode 100644 index 000000000..2b846c4a4 --- /dev/null +++ b/temporal/tests/_support/AcceptanceTester.php @@ -0,0 +1,30 @@ +haveHttpHeader('Content-Type', 'application/json'); + $I->sendPOST( + '/auth/', + [ + 'login' => 'Opal1144', + 'password' => 'Opal1144', + ] + ); + $I->seeResponseCodeIs(HttpCode::OK); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'success', + 'error_message' => '', + 'error_code' => null, + ] + ); + + $response = Json::decode($I->grabResponse()); + $I->seeInDatabase( + 'user', + [ + 'id' => 1, + 'token' => $response['data']['token'], + ] + ); + } + + public function logout(AcceptanceTester $I): void + { + $I->haveHttpHeader( + 'X-Api-Key', + 'lev1ZsWCzqrMlXRI2sT8h4ApYpSgBMl1xf6D4bCRtiKtDqw6JN36yLznargilQ_rEJz9zTfcUxm53PLODCToF9gGin38Rd4NkhQPOVeH5VvZvBaQlUg64E6icNCubiAv' + ); + + $I->sendPOST( + '/logout/' + ); + + $I->seeResponseCodeIs(HttpCode::OK); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'success', + 'error_message' => '', + 'error_code' => null, + ] + ); + + $I->dontSeeInDatabase( + 'user', + [ + 'id' => 1, + 'token' => 'lev1ZsWCzqrMlXRI2sT8h4ApYpSgBMl1xf6D4bCRtiKtDqw6JN36yLznargilQ_rEJz9zTfcUxm53PLODCToF9gGin38Rd4NkhQPOVeH5VvZvBaQlUg64E6icNCubiAv', + ] + ); + } + + public function logoutWithBadToken(AcceptanceTester $I): void + { + $I->haveHttpHeader( + 'X-Api-Key', + 'bad-token' + ); + + $I->haveHttpHeader( + 'Accept', + 'application/json' + ); + + $I->sendPOST( + '/logout/' + ); + + $I->seeResponseCodeIs(HttpCode::UNAUTHORIZED); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'failed', + 'error_message' => 'Unauthorised request', + 'error_code' => HttpCode::UNAUTHORIZED, + 'data' => null, + ] + ); + } +} diff --git a/temporal/tests/acceptance/BlogCest.php b/temporal/tests/acceptance/BlogCest.php new file mode 100644 index 000000000..6e9c9eb76 --- /dev/null +++ b/temporal/tests/acceptance/BlogCest.php @@ -0,0 +1,63 @@ +sendGET( + '/blog/', + [ + 'page' => 2, + ] + ); + $I->seeResponseCodeIs(HttpCode::OK); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'success', + 'error_message' => '', + 'error_code' => null, + 'data' => [ + 'paginator' => [ + 'pageSize' => 10, + 'currentPage' => 2, + 'totalPages' => 2, + ], + 'posts' => [ + [ + 'id' => 11, + 'title' => 'Eveniet est nam sapiente odit architecto et.', + ], + ], + ], + ] + ); + } + + public function view(AcceptanceTester $I): void + { + $I->sendGET('/blog/11'); + $I->seeResponseCodeIs(HttpCode::OK); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'success', + 'error_message' => '', + 'error_code' => null, + 'data' => [ + 'post' => [ + 'id' => 11, + 'title' => 'Eveniet est nam sapiente odit architecto et.', + ], + ], + ] + ); + } +} diff --git a/temporal/tests/acceptance/SiteCest.php b/temporal/tests/acceptance/SiteCest.php new file mode 100644 index 000000000..f5ea502c4 --- /dev/null +++ b/temporal/tests/acceptance/SiteCest.php @@ -0,0 +1,44 @@ +sendGET('/'); + $I->seeResponseCodeIs(HttpCode::OK); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'success', + 'error_message' => '', + 'error_code' => null, + 'data' => [ + 'version' => '3.0', + 'author' => 'yiisoft', + ], + ] + ); + } + + public function testNotFoundPage(AcceptanceTester $I): void + { + $I->sendGET('/not_found_page'); + $I->seeResponseCodeIs(HttpCode::NOT_FOUND); + $I->seeResponseIsJson(); + $I->seeResponseContainsJson( + [ + 'status' => 'failed', + 'error_message' => 'Page not found', + 'error_code' => 404, + 'data' => null, + ] + ); + } +} diff --git a/temporal/tests/functional/EventListenerConfigurationTest.php b/temporal/tests/functional/EventListenerConfigurationTest.php new file mode 100644 index 000000000..0e06dcbe8 --- /dev/null +++ b/temporal/tests/functional/EventListenerConfigurationTest.php @@ -0,0 +1,28 @@ +get('console')))->get(ContainerInterface::class); + $checker = $container->get(ListenerConfigurationChecker::class); + $checker->check($config->get('events-console')); + + self::assertInstanceOf(ListenerConfigurationChecker::class, $checker); + } +} diff --git a/temporal/yii b/temporal/yii new file mode 100755 index 000000000..8d2bd2fa6 --- /dev/null +++ b/temporal/yii @@ -0,0 +1,12 @@ +#!/usr/bin/env php +run(); diff --git a/temporal/yii.bat b/temporal/yii.bat new file mode 100644 index 000000000..7b130a117 --- /dev/null +++ b/temporal/yii.bat @@ -0,0 +1,6 @@ +@echo off +@setlocal +set YII_PATH=%~dp0 +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe +"%PHP_COMMAND%" "%YII_PATH%yii" %* +@endlocal