├── .bowerrc ├── .dockerignore ├── .github └── workflows │ └── tencent.yml ├── .gitignore ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── Dockerfile ├── LICENSE.md ├── README.md ├── app ├── Behavior │ ├── ArticleBehavior.php │ ├── ArticleCategoryBehavior.php │ ├── CreatorBehavior.php │ └── TimestampBehavior.php ├── Console │ ├── Controller │ │ ├── Controller.php │ │ ├── GcController.php │ │ ├── HelloController.php │ │ └── RequirementController.php │ ├── Job │ │ └── HelloJob.php │ └── Model │ │ └── .gitkeep ├── Db │ ├── ActiveQuery.php │ └── Migration.php ├── Exception │ └── .gitkeep ├── Factory │ ├── ArticleLikeFactory.php │ ├── ArticleMetaFactory.php │ └── SessionFactory.php ├── Form │ ├── BaseForm.php │ ├── Form.php │ └── FormTrait.php ├── Gii │ └── Generator │ │ └── Module │ │ └── Generator.php ├── Helper │ └── DbHelper.php ├── Http │ ├── Api │ │ ├── Backend │ │ │ ├── Controller │ │ │ │ ├── ActiveController.php │ │ │ │ ├── ArticleCategoryController.php │ │ │ │ ├── ArticleController.php │ │ │ │ ├── AuthItemController.php │ │ │ │ ├── Controller.php │ │ │ │ ├── ControllerTrait.php │ │ │ │ ├── MyController.php │ │ │ │ ├── MySessionController.php │ │ │ │ ├── PermissionController.php │ │ │ │ ├── RoleController.php │ │ │ │ ├── SessionController.php │ │ │ │ ├── SettingController.php │ │ │ │ ├── UploadController.php │ │ │ │ └── UserController.php │ │ │ ├── Form │ │ │ │ ├── ChangePasswordForm.php │ │ │ │ ├── LoginForm.php │ │ │ │ ├── LogoutForm.php │ │ │ │ ├── MyInfoForm.php │ │ │ │ ├── SessionRefreshForm.php │ │ │ │ └── UserForm.php │ │ │ ├── Model │ │ │ │ ├── Article.php │ │ │ │ ├── ArticleCategory.php │ │ │ │ ├── AuthItem.php │ │ │ │ ├── Permission.php │ │ │ │ ├── Role.php │ │ │ │ ├── Session.php │ │ │ │ ├── Setting.php │ │ │ │ └── User.php │ │ │ ├── Module.php │ │ │ └── Module │ │ │ │ └── V1 │ │ │ │ ├── Controller │ │ │ │ ├── ArticleCategoryController.php │ │ │ │ ├── ArticleController.php │ │ │ │ ├── MyController.php │ │ │ │ ├── MySessionController.php │ │ │ │ ├── PermissionController.php │ │ │ │ ├── RoleController.php │ │ │ │ ├── SessionController.php │ │ │ │ ├── SettingController.php │ │ │ │ ├── UploadController.php │ │ │ │ └── UserController.php │ │ │ │ └── Module.php │ │ ├── Controller │ │ │ └── .gitkeep │ │ ├── Form │ │ │ ├── ArticleDislikeForm.php │ │ │ ├── ArticleLikeForm.php │ │ │ ├── BaseArticleLikeForm.php │ │ │ └── UserTrait.php │ │ ├── Frontend │ │ │ ├── Controller │ │ │ │ ├── ActiveController.php │ │ │ │ ├── ArticleCommentController.php │ │ │ │ ├── ArticleController.php │ │ │ │ ├── Controller.php │ │ │ │ └── ControllerTrait.php │ │ │ ├── Module.php │ │ │ └── Module │ │ │ │ └── V1 │ │ │ │ ├── Controller │ │ │ │ ├── ArticleCommentController.php │ │ │ │ └── ArticleController.php │ │ │ │ └── Module.php │ │ ├── Model │ │ │ ├── Article.php │ │ │ └── ArticleComment.php │ │ └── Module.php │ ├── Asset │ │ └── AppAsset.php │ ├── Controller │ │ ├── ArticleController.php │ │ ├── Controller.php │ │ └── SiteController.php │ ├── Filter │ │ ├── Auth │ │ │ └── Authenticator.php │ │ ├── ContentNegotiator.php │ │ └── LanguagePickerInterface.php │ ├── Form │ │ ├── ContactForm.php │ │ ├── LoginForm.php │ │ ├── PasswordResetRequestForm.php │ │ ├── ResendVerificationEmailForm.php │ │ ├── ResetPasswordForm.php │ │ ├── SignupForm.php │ │ ├── UploadForm.php │ │ └── VerifyEmailForm.php │ ├── Rest │ │ ├── ActiveController.php │ │ ├── Controller.php │ │ ├── CreateAction.php │ │ ├── DeleteAction.php │ │ ├── OptionsAction.php │ │ ├── SafeActionTrait.php │ │ ├── Serializer.php │ │ └── UpdateAction.php │ ├── User │ │ └── LogoutInterface.php │ ├── Web │ │ └── ErrorHandler.php │ └── Widget │ │ └── Alert.php ├── Job │ └── MailJob.php ├── Model │ ├── ActiveRecord.php │ ├── Article.php │ ├── ArticleCategory.php │ ├── ArticleComment.php │ ├── ArticleLike.php │ ├── ArticleMeta.php │ ├── AuthItem.php │ ├── Session.php │ ├── SoftDeleteInterface.php │ ├── SoftDeleteTrait.php │ ├── StatusInterface.php │ ├── StatusTrait.php │ └── User.php ├── Queue │ ├── Gii │ │ └── Generator.php │ ├── Job.php │ ├── JobEvent.php │ ├── JobPreventedException.php │ └── RetryableTrait.php ├── Rbac │ └── Rule │ │ ├── ArticleDeleteRule.php │ │ ├── RoleDeleteRule.php │ │ ├── Rule.php │ │ └── UserDeleteRule.php ├── Trait │ └── .gitkeep └── Validator │ ├── PasswordValidator.php │ └── UrlValidator.php ├── bin ├── init ├── init.bat ├── yii └── yii.bat ├── codeception.dist.yml ├── composer.json ├── composer.lock ├── conf ├── app.service ├── crontab ├── nginx.conf └── queue.service ├── config ├── .gitignore ├── bootstrap.php ├── common.php ├── console.php ├── debug.php ├── gii.php ├── params.php ├── routes.php ├── test.php └── web.php ├── docs └── en │ ├── DOCKER.md │ └── INSTALLATION.md ├── public ├── .htaccess ├── css │ ├── app.css │ └── article.css ├── img │ ├── yii2-app.gif │ └── yii2-vue.gif ├── index.php ├── js │ ├── app.js │ └── article.js └── robots.txt ├── resources ├── console │ └── views │ │ └── migration.php ├── docker │ ├── cron │ │ ├── crontab │ │ └── image │ │ │ ├── Dockerfile │ │ │ └── crontab │ ├── mysql │ │ └── conf.d │ │ │ └── mysqld.cnf │ ├── nginx │ │ ├── conf.d │ │ │ └── default.conf │ │ └── nginx.conf │ ├── php │ │ ├── image │ │ │ └── Dockerfile │ │ └── php.ini │ ├── queue │ │ └── image │ │ │ └── Dockerfile │ └── redis │ │ └── redis.conf ├── environments │ ├── dev │ │ ├── bin │ │ │ ├── yii_test │ │ │ └── yii_test.bat │ │ └── config │ │ │ ├── bootstrap-local.php │ │ │ ├── common-local.php │ │ │ ├── console-local.php │ │ │ ├── params-local.php │ │ │ ├── test-local.php │ │ │ └── web-local.php │ ├── docker │ │ ├── .env │ │ ├── bin │ │ │ ├── yii_test │ │ │ └── yii_test.bat │ │ ├── config │ │ │ ├── bootstrap-local.php │ │ │ ├── common-local.php │ │ │ ├── console-local.php │ │ │ ├── params-local.php │ │ │ ├── test-local.php │ │ │ └── web-local.php │ │ └── docker-compose.yml │ ├── dockerenv │ │ ├── bin │ │ │ ├── yii_test │ │ │ └── yii_test.bat │ │ └── config │ │ │ ├── bootstrap-local.php │ │ │ ├── common-local.php │ │ │ ├── console-local.php │ │ │ ├── params-local.php │ │ │ ├── test-local.php │ │ │ └── web-local.php │ ├── index.php │ └── prod │ │ └── config │ │ ├── bootstrap-local.php │ │ ├── common-local.php │ │ ├── console-local.php │ │ ├── params-local.php │ │ ├── test-local.php │ │ └── web-local.php ├── gii │ └── generators │ │ └── module │ │ └── views │ │ ├── controller.php │ │ ├── form.php │ │ ├── module.php │ │ └── view.php ├── mail │ ├── emailVerify-html.php │ ├── emailVerify-text.php │ ├── hello.php │ ├── layouts │ │ ├── html.php │ │ └── text.php │ ├── passwordResetToken-html.php │ └── passwordResetToken-text.php ├── messages │ ├── zh-CN │ │ └── app.php │ └── zh-TW │ │ └── app.php ├── migrations │ ├── m190617_031446_table_user.php │ ├── m190722_062914_table_session.php │ ├── m190724_022733_auth_init.php │ ├── m190724_031648_data_user.php │ ├── m190725_110548_setting_init.php │ ├── m190731_024219_table_article.php │ ├── m190822_022858_table_article_meta.php │ ├── m190822_034841_table_article_category.php │ ├── m190822_070400_data_article_category.php │ ├── m190822_070404_data_article.php │ ├── m190829_053119_table_article_like.php │ └── m190829_093604_article_comment.php ├── queue │ └── gii │ │ └── views │ │ ├── form.php │ │ └── job.php ├── requirements.php └── views │ ├── article │ ├── comments.php │ ├── index.php │ └── view.php │ ├── layouts │ └── main.php │ └── site │ ├── about.php │ ├── contact.php │ ├── error.php │ ├── index.php │ ├── login.php │ ├── requestPasswordResetToken.php │ ├── resendVerificationEmail.php │ ├── resetPassword.php │ └── signup.php └── tests ├── Acceptance ├── HomeCest.php └── _bootstrap.php ├── Fixture └── UserFixture.php ├── Functional ├── Form │ └── LoginFormTest.php └── _bootstrap.php ├── TestCase.php ├── Unit ├── Console │ └── Job │ │ └── HelloJobTest.php ├── Factory │ ├── ArticleMetaFactoryTest.php │ └── SessionFactoryTest.php ├── Helper │ └── DbHelperTest.php ├── Validator │ └── UrlValidatorTest.php └── _bootstrap.php ├── _bootstrap.php ├── _data ├── .gitkeep └── user.php ├── _output └── .gitignore ├── _support ├── AcceptanceTester.php ├── FunctionalTester.php ├── Helper │ ├── Acceptance.php │ ├── Functional.php │ └── Unit.php ├── UnitTester.php └── _generated │ └── .gitignore ├── acceptance.suite.yml ├── codeception.yml ├── config.php ├── functional.suite.yml └── unit.suite.yml /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower-asset" 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /.github/workflows/tencent.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a docker container, publish it to Tencent Kubernetes Engine (TKE) registry. 2 | # 3 | # To configure this workflow: 4 | # 5 | # 1. Set up secrets in your workspace: 6 | # - TENCENT_CLOUD_ACCOUNT_ID with Tencent Cloud account id 7 | # - TKE_REGISTRY_PASSWORD with TKE registry password 8 | # 9 | # 2. Change the values for the TKE_IMAGE_URL environment variables (below). 10 | 11 | name: Tencent Kubernetes Engine 12 | 13 | on: 14 | push: 15 | branches: 16 | - master 17 | 18 | # Environment variables available to all jobs and steps in this workflow 19 | env: 20 | TKE_IMAGE_URL: hkccr.ccs.tencentyun.com/razonyang/yii2-app-template 21 | 22 | jobs: 23 | setup-build-publish: 24 | name: Setup, Build and Publish 25 | runs-on: ubuntu-latest 26 | steps: 27 | 28 | - name: Checkout 29 | uses: actions/checkout@v2 30 | 31 | # Build 32 | - name: Build Docker image 33 | run: | 34 | docker build -t ${TKE_IMAGE_URL}:${GITHUB_SHA} -t ${TKE_IMAGE_URL}:latest . 35 | - name: Login TKE Registry 36 | run: | 37 | docker login -u ${{ secrets.TENCENT_CLOUD_ACCOUNT_ID }} -p ${{ secrets.TKE_REGISTRY_PASSWORD }} ${TKE_IMAGE_URL} 38 | # Push the Docker image to TKE Registry 39 | - name: Publish 40 | run: | 41 | docker push ${TKE_IMAGE_URL}:${GITHUB_SHA} 42 | docker push ${TKE_IMAGE_URL}:latest 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # yii console commands 2 | /bin/yii_test 3 | /bin/yii_test.bat 4 | 5 | # phpstorm project files 6 | .idea 7 | 8 | # netbeans project files 9 | nbproject 10 | 11 | # zend studio for eclipse project files 12 | .buildpath 13 | .project 14 | .settings 15 | 16 | # windows thumbnail cache 17 | Thumbs.db 18 | 19 | # composer vendor dir 20 | /vendor 21 | 22 | # composer itself is not needed 23 | # composer.lock 24 | composer.phar 25 | 26 | # Mac DS_Store Files 27 | .DS_Store 28 | 29 | # phpunit itself is not needed 30 | phpunit.phar 31 | # local phpunit config 32 | /phpunit.xml 33 | .phpunit.result.cache 34 | 35 | # ignore generated files 36 | /public/index-test.php 37 | /public/assets/ 38 | /public/resources/ 39 | 40 | # runtime 41 | /runtime/ 42 | 43 | # docker 44 | /.env 45 | /docker-compose.yml 46 | 47 | # vim 48 | tags 49 | .tags -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | nodes: 3 | analysis: 4 | tests: 5 | override: 6 | - php-scrutinizer-run 7 | 8 | checks: 9 | php: true 10 | tools: 11 | php_code_coverage: 12 | enabled: true 13 | external_code_coverage: 14 | timeout: 600 -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | 3 | finder: 4 | exclude: 5 | - docs 6 | - vendor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | 4 | dist: xenial 5 | 6 | php: 7 | - 7.1 8 | - 7.2 9 | - 7.3 10 | 11 | # faster builds on new travis setup not using sudo 12 | sudo: false 13 | 14 | services: 15 | - mysql 16 | - redis-server 17 | 18 | # cache vendor dirs 19 | cache: 20 | directories: 21 | - $HOME/.composer/cache 22 | 23 | install: 24 | - travis_retry composer self-update && composer --version 25 | - export PATH="$HOME/.composer/vendor/bin:$PATH" 26 | - travis_retry composer install --prefer-dist --no-interaction 27 | 28 | before_script: 29 | - mysql -e 'CREATE DATABASE yiitest;' 30 | - php bin/init --env=Development --overwrite=y 31 | - php bin/yii_test migrate/fresh --interactive=0 32 | - php bin/yii_test serve & 33 | 34 | script: 35 | - vendor/bin/codecept run --coverage --coverage-xml 36 | 37 | after_script: 38 | - wget -c https://scrutinizer-ci.com/ocular.phar 39 | - php ocular.phar code-coverage:upload --format=php-clover tests/_output/coverage.xml 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM yiisoftware/yii2-php:7.4-apache 2 | 3 | ENV APACHE_DOCUMENT_ROOT /app/public 4 | 5 | RUN sed -ri -e 's!/app/web!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf 6 | 7 | COPY --chown=www-data:www-data . /app 8 | 9 | RUN git config --global url."git@github.com:".insteadOf "https://github.com/" 10 | 11 | RUN composer install --prefer-dist --no-dev --optimize-autoloader 12 | 13 | RUN /app/bin/init --env=DockerEnv --overwrite=No 14 | 15 | RUN chown -R www-data:www-data /app/runtime 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software LLC nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /app/Behavior/ArticleBehavior.php: -------------------------------------------------------------------------------- 1 | 'saveMeta', 16 | Article::EVENT_AFTER_UPDATE => 'saveMeta', 17 | Article::EVENT_AFTER_DELETE => 'afterDelete', 18 | ]; 19 | } 20 | 21 | public function saveMeta(Event $event) 22 | { 23 | /** @var Article $model */ 24 | $model = $event->sender; 25 | 26 | $meta = ArticleMetaFactory::findByArticleId($model->id); 27 | if (!$meta) { 28 | $meta = ArticleMetaFactory::create($model->id, $model->content); 29 | } else { 30 | $meta->content = $model->content; 31 | } 32 | if (!$meta->save()) { 33 | Yii::error($meta->getErrors(), __METHOD__); 34 | throw new \Exception('Unable to save article meta'); 35 | } 36 | } 37 | 38 | public function afterDelete(Event $event) 39 | { 40 | /** @var Article $model */ 41 | $model = $event->sender; 42 | 43 | $meta = ArticleMetaFactory::findByArticleId($model->id); 44 | if ($meta && $meta->delete() === false) { 45 | throw new \Exception('Unable to delete article meta'); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Behavior/ArticleCategoryBehavior.php: -------------------------------------------------------------------------------- 1 | 'beforeDelete', 16 | ]; 17 | } 18 | 19 | public function beforeDelete(Event $event) 20 | { 21 | /** @var ArticleCategory $model */ 22 | $model = $event->sender; 23 | $count = Article::find() 24 | ->where([ 25 | 'category_id' => $model->id, 26 | 'is_deleted' => 0, 27 | ]) 28 | ->count(); 29 | if ($count > 0) { 30 | throw new ForbiddenHttpException('Unable to delete category since there are articles belong to it'); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Behavior/CreatorBehavior.php: -------------------------------------------------------------------------------- 1 | attributes = [ 18 | $this->creatorAttribute => [ 19 | ActiveRecord::EVENT_BEFORE_VALIDATE => function (ModelEvent $event, $attribute) { 20 | /** @var \yii\db\ActiveRecord $model */ 21 | $model = $event->sender; 22 | return $model->getIsNewRecord() ? Yii::$app->getUser()->getId() : $model->$attribute; 23 | }, 24 | ], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Behavior/TimestampBehavior.php: -------------------------------------------------------------------------------- 1 | log($message, $level, $category); 20 | $this->stdout($message . PHP_EOL); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Console/Controller/GcController.php: -------------------------------------------------------------------------------- 1 | logAndPrint(sprintf('Deleted %d expired user sessions', $deleted)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Console/Controller/HelloController.php: -------------------------------------------------------------------------------- 1 | stdout('Hello ' . $name . PHP_EOL); 12 | } 13 | 14 | public function actionMail($to, $name = 'Foo') 15 | { 16 | $mailer = Yii::$app->getMailer(); 17 | $sent = $mailer->compose('hello', ['name' => $name]) 18 | ->setTo($to) 19 | ->setFrom(Yii::$app->get('settingManager')->get('mailer.username')) 20 | ->setSubject('Hello') 21 | ->send(); 22 | if (!$sent) { 23 | $this->stdout('Send failure' . PHP_EOL); 24 | return; 25 | } 26 | 27 | $this->stdout('Sent successfully' . PHP_EOL); 28 | } 29 | 30 | public function actionJob($name = 'World') 31 | { 32 | $job = new HelloJob(['name' => $name]); 33 | $id = Yii::$app->get('queue')->push($job); 34 | $this->stdout(HelloJob::class . '#' . $id . PHP_EOL); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Console/Controller/RequirementController.php: -------------------------------------------------------------------------------- 1 | name; 14 | echo $message . PHP_EOL; 15 | Yii::info($message, __METHOD__); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Console/Model/.gitkeep: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /app/Db/ActiveQuery.php: -------------------------------------------------------------------------------- 1 | $articleId, 13 | 'user_id' => $userId, 14 | ]); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Factory/ArticleMetaFactory.php: -------------------------------------------------------------------------------- 1 | $articleId, 13 | ]); 14 | } 15 | 16 | public static function create(int $articleId, string $content): ArticleMeta 17 | { 18 | return new ArticleMeta([ 19 | 'article_id' => $articleId, 20 | 'content' => $content, 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Factory/SessionFactory.php: -------------------------------------------------------------------------------- 1 | getUserIP(); 24 | $userAgent = mb_substr($request->getUserAgent(), 0, 255); 25 | $token = static::generateToken($userId); 26 | $refeshToken = static::generateRefreshToken($userId); 27 | $now = time(); 28 | return new Session([ 29 | 'id' => Uuid::uuid4()->toString(), 30 | 'token' => $token, 31 | 'refresh_token' => $refeshToken, 32 | 'user_id' => $userId, 33 | 'expire_time' => $now + $duration, 34 | 'refresh_token_expire_time' => $now + $refeshDuration, 35 | 'ip_address' => $ip, 36 | 'user_agent' => $userAgent, 37 | ]); 38 | } 39 | 40 | /** 41 | * Generates access token. 42 | * 43 | * @param int $userId user ID. 44 | * 45 | * @return string 46 | */ 47 | public static function generateToken(int $userId): string 48 | { 49 | return ($userId % 10) . Yii::$app->getSecurity()->generateRandomString(31); 50 | } 51 | 52 | /** 53 | * Generates refresh token. 54 | * 55 | * @param int $userId user ID. 56 | * 57 | * @return string 58 | */ 59 | public static function generateRefreshToken(int $userId): string 60 | { 61 | return ($userId % 10) . Yii::$app->getSecurity()->generateRandomString(63); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/Form/BaseForm.php: -------------------------------------------------------------------------------- 1 | validate()) { 14 | return $this; 15 | } 16 | 17 | $data = $this->handleInternal(); 18 | return $data; 19 | } catch (\Throwable $e) { 20 | if ($this->hasErrors()) { 21 | return $this; 22 | } 23 | 24 | throw $e; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Helper/DbHelper.php: -------------------------------------------------------------------------------- 1 | getDb() will be used if no specified. 15 | * @param null|string $isolationLevel 16 | * 17 | * @throws \Throwable 18 | */ 19 | public static function transaction(callable $callback, array $parameters = [], ?Connection $db = null, ?string $isolationLevel = null) 20 | { 21 | $db = $db ?? Yii::$app->getDb(); 22 | $transaction = $db->beginTransaction($isolationLevel); 23 | try { 24 | $result = call_user_func_array($callback, $parameters); 25 | 26 | $transaction->commit(); 27 | 28 | return $result; 29 | } catch (\Throwable $e) { 30 | Yii::error($e, __METHOD__); 31 | $transaction->rollBack(); 32 | throw $e; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/ActiveController.php: -------------------------------------------------------------------------------- 1 | alias('c') 15 | ->orderBy([ 16 | 'c.name' => SORT_ASC 17 | ]); 18 | } 19 | 20 | public function searchModel() 21 | { 22 | return (new DynamicModel(['id' => '', 'name' => ''])) 23 | ->addRule(['name'], 'string') 24 | ->addRule(['id'], 'number'); 25 | } 26 | 27 | protected function applyFilter($query, $model, $filter) 28 | { 29 | foreach (['name'] as $name) { 30 | if (!empty($model->$name)) { 31 | $query->andWhere(['LIKE', 'c.' . $name, $model->$name]); 32 | } 33 | } 34 | foreach (['id'] as $name) { 35 | if (is_numeric($model->$name)) { 36 | $query->andWhere(['c.' . $name => $model->$name]); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/ArticleController.php: -------------------------------------------------------------------------------- 1 | alias('a') 15 | ->orderBy([ 16 | 'a.id' => SORT_DESC 17 | ]) 18 | ->andWhere([ 19 | 'a.is_deleted' => 0, 20 | ]); 21 | } 22 | 23 | public function searchModel() 24 | { 25 | return (new DynamicModel(['id' => '', 'title' => '', 'author' => '', 'summary' => '', 'status' => '', 'category_id' => ''])) 26 | ->addRule(['title', 'author', 'summary'], 'string') 27 | ->addRule(['id', 'status', 'category_id'], 'number'); 28 | } 29 | 30 | protected function applyFilter($query, $model, $filter) 31 | { 32 | foreach (['title', 'author', 'summary'] as $name) { 33 | if (!empty($model->$name)) { 34 | $query->andWhere(['LIKE', 'a.' . $name, $model->$name]); 35 | } 36 | } 37 | foreach (['id', 'status', 'category_id'] as $name) { 38 | if (is_numeric($model->$name)) { 39 | $query->andWhere(['a.' . $name => $model->$name]); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/AuthItemController.php: -------------------------------------------------------------------------------- 1 | alias('ai') 14 | ->orderBy(['ai.name' => SORT_ASC]) 15 | ->andWhere(['ai.type' => $this->getType()]); 16 | } 17 | 18 | public function searchModel() 19 | { 20 | return (new DynamicModel(['name' => '', 'description' => ''])) 21 | ->addRule(['name', 'description'], 'string'); 22 | } 23 | 24 | public function applyFilter($query, $model, $filter) 25 | { 26 | foreach (['name', 'description'] as $name) { 27 | if (!empty($model->$name)) { 28 | $query->andWhere(['LIKE', 'ai.' . $name, $model->$name]); 29 | } 30 | } 31 | } 32 | 33 | public function findModelById($id, $action) 34 | { 35 | return $action->modelClass::findOne([ 36 | 'name' => $id, 37 | 'type' => $this->getType(), 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/Controller.php: -------------------------------------------------------------------------------- 1 | OptionsAction::class, 17 | ]; 18 | 19 | return $acitons; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/ControllerTrait.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'class' => Cors::class, 19 | 'cors' => [ 20 | 'Origin' => Yii::$app->params['api.cors.origin'], 21 | 'Access-Control-Request-Method' => Yii::$app->params['api.cors.methods'], 22 | 'Access-Control-Request-Headers' => Yii::$app->params['api.cors.headers'], 23 | 'Access-Control-Allow-Credentials' => Yii::$app->params['api.cors.allowCredentials'], 24 | 'Access-Control-Max-Age' => Yii::$app->params['api.cors.maxAge'], 25 | 'Access-Control-Expose-Headers' => Yii::$app->params['api.cors.exposeHeaders'], 26 | ], 27 | ], 28 | 'authenticator' => [ 29 | 'class' => Authenticator::class, 30 | 'optional' => [ 31 | 'options', 32 | ], 33 | 'authMethods' => [ 34 | 'query' => [ 35 | 'class' => QueryParamAuth::class, 36 | 'tokenParam' => 'access_token', 37 | ], 38 | 'bearer' => [ 39 | 'class' => HttpBearerAuth::class, 40 | ], 41 | ], 42 | ], 43 | 'verb' => [ 44 | 'class' => VerbFilter::class, 45 | 'actions' => $this->verbs(), 46 | ], 47 | 'rateLimiter' => [ 48 | 'class' => RateLimiter::class, 49 | 'redis' => 'redis', 50 | 'capacity' => Yii::$app->params['api.rateLimiter.capacity'], 51 | 'rate' => Yii::$app->params['api.rateLimiter.rate'], 52 | 'limitPeriod' => Yii::$app->params['api.rateLimiter.limitPeriod'], 53 | 'prefix' => Yii::$app->params['api.rateLimiter.prefix'], 54 | 'ttl' => Yii::$app->params['api.rateLimiter.ttl'], 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | protected function verbs() 63 | { 64 | return []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/MyController.php: -------------------------------------------------------------------------------- 1 | handle(); 14 | } 15 | 16 | public function actionPassword() 17 | { 18 | $form = new ChangePasswordForm(); 19 | $form->load(Yii::$app->getRequest()->post(), ''); 20 | return $form->handle(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/MySessionController.php: -------------------------------------------------------------------------------- 1 | andWhere(['us.user_id' => Yii::$app->getUser()->getId()]); 13 | } 14 | 15 | public function findModelById($id, $action) 16 | { 17 | return Session::findOne([ 18 | 'id' => $id, 19 | 'user_id' => Yii::$app->getUser()->getId(), 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/PermissionController.php: -------------------------------------------------------------------------------- 1 | $actions['index'], 17 | 'options' => $actions['options'], 18 | ]; 19 | } 20 | 21 | protected function getType() 22 | { 23 | return Item::TYPE_PERMISSION; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/RoleController.php: -------------------------------------------------------------------------------- 1 | $actions['index'], 17 | 'delete' => $actions['delete'], 18 | 'view' => $actions['view'], 19 | 'options' => $actions['options'], 20 | ]; 21 | } 22 | 23 | public function getPermission($action) 24 | { 25 | // return 'userSession' . ucfirst($action); 26 | } 27 | 28 | public function searchModel() 29 | { 30 | return (new \yii\base\DynamicModel(['id', 'username' => null, 'email' => null, 'status' => null])) 31 | ->addRule(['id', 'status'], 'integer') 32 | ->addRule(['username', 'email'], 'trim') 33 | ->addRule(['username', 'email'], 'string'); 34 | } 35 | 36 | protected function getQuery($action) 37 | { 38 | return parent::getQuery($action) 39 | ->alias('us') 40 | ->orderBy(['us.create_time' => SORT_DESC]); 41 | } 42 | 43 | protected function applyFilter($query, $model, $filter) 44 | { 45 | foreach (['id', 'username', 'email'] as $name) { 46 | if (!empty($model->$name)) { 47 | $query->andFilterWhere(['LIKE', 'u.' . $name, $model->$name]); 48 | } 49 | } 50 | 51 | if (is_numeric($model->status)) { 52 | $query->andWhere(['u.status' => intval($model->status)]); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/SettingController.php: -------------------------------------------------------------------------------- 1 | $actions['index'], 17 | 'view' => $actions['view'], 18 | 'update' => $actions['update'], 19 | 'options' => $actions['options'], 20 | ]; 21 | } 22 | 23 | public function searchModel() 24 | { 25 | return (new DynamicModel(['id' => '', 'description' => ''])) 26 | ->addRule(['id', 'description'], 'string'); 27 | } 28 | 29 | protected function getQuery($action) 30 | { 31 | return parent::getQuery($action) 32 | ->alias('s') 33 | ->orderBy(['id' => SORT_ASC]); 34 | } 35 | 36 | protected function applyFilter($query, $model, $filter) 37 | { 38 | foreach (['id', 'description'] as $name) { 39 | if (!empty($model->$name)) { 40 | $query->andWhere(['LIKE', 's.' . $name, $model->$name]); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/UploadController.php: -------------------------------------------------------------------------------- 1 | getConfig('image')); 13 | $form->load(Yii::$app->getRequest()->post(), ''); 14 | $form->file = UploadedFile::getInstanceByName('file'); 15 | return $form->handle(); 16 | } 17 | 18 | public function actionVideos() 19 | { 20 | $form = new UploadForm($this->getConfig('video')); 21 | $form->load(Yii::$app->getRequest()->post(), ''); 22 | $form->file = UploadedFile::getInstanceByName('file'); 23 | return $form->handle(); 24 | } 25 | 26 | private function getConfig(string $type): array 27 | { 28 | /** @var \RazonYang\Yii2\Setting\ManagerInterface $setting */ 29 | $setting = Yii::$app->get('settingManager'); 30 | $prefix = 'upload.' . $type . '.'; 31 | $parameters = ['maxSize', 'extensions']; 32 | $config = []; 33 | foreach ($parameters as $parameter) { 34 | $config[$parameter] = $setting->get($prefix.$parameter); 35 | } 36 | return $config; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Controller/UserController.php: -------------------------------------------------------------------------------- 1 | load(Yii::$app->getRequest()->post(), ''); 30 | return $form->handle(); 31 | } 32 | 33 | public function actionRefreshAccessToken() 34 | { 35 | $form = new SessionRefreshForm(); 36 | $form->load(Yii::$app->getRequest()->post(), ''); 37 | return $form->handle(); 38 | } 39 | 40 | public function actionLogout() 41 | { 42 | $form = new LogoutForm(); 43 | return $form->handle(); 44 | } 45 | 46 | public function searchModel() 47 | { 48 | return (new \yii\base\DynamicModel(['id', 'username' => null, 'email' => null, 'status' => null])) 49 | ->addRule(['id', 'status'], 'integer') 50 | ->addRule(['username', 'email'], 'trim') 51 | ->addRule(['username', 'email'], 'string'); 52 | } 53 | 54 | protected function getQuery($action) 55 | { 56 | return parent::getQuery($action) 57 | ->alias('u') 58 | ->andWhere(['is_deleted' => 0]) 59 | ->orderBy(['id' => SORT_DESC]); 60 | } 61 | 62 | protected function applyFilter($query, $model, $filter) 63 | { 64 | foreach (['id', 'username', 'email'] as $name) { 65 | if (!empty($model->$name)) { 66 | $query->andFilterWhere(['LIKE', 'u.' . $name, $model->$name]); 67 | } 68 | } 69 | 70 | if (is_numeric($model->status)) { 71 | $query->andWhere(['u.status' => intval($model->status)]); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Form/ChangePasswordForm.php: -------------------------------------------------------------------------------- 1 | 'password', 'operator' => '!='], 27 | ['new_password', PasswordValidator::class], 28 | ]); 29 | } 30 | 31 | public function validatePassword($attribute) 32 | { 33 | $user = $this->getUser(); 34 | if (!$user || !$user->validatePassword($this->$attribute)) { 35 | $this->addError($attribute, Yii::t('app', 'Original password is incorrect')); 36 | } 37 | } 38 | 39 | protected function handleInternal() 40 | { 41 | $user = $this->getUser(); 42 | $user->setPassword($this->new_password); 43 | if (!$user->save()) { 44 | Yii::error($user->getErrors()); 45 | throw new \RuntimeException('Unable to change password'); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Form/LoginForm.php: -------------------------------------------------------------------------------- 1 | getDb()->beginTransaction(); 18 | try { 19 | $user = $this->getUser(); 20 | 21 | $session = SessionFactory::create( 22 | $user->id, 23 | Yii::$app->params['user.session.duration'], 24 | Yii::$app->params['user.session.refreshTokenDuration'], 25 | Yii::$app->getRequest() 26 | ); 27 | if (!$session->save()) { 28 | Yii::error($session->getErrors(), __METHOD__); 29 | throw new \RuntimeException('Unable to save session' . \yii\helpers\VarDumper::dumpAsString($session->getErrors())); 30 | } 31 | 32 | Yii::$app->user->login($user, $this->rememberMe ? 3600 * 24 * 30 : 0); 33 | $transaction->commit(); 34 | 35 | return [ 36 | 'id' => $user->id, 37 | 'username' => $user->username, 38 | 'email' => $user->email, 39 | 'token' => $session->token, 40 | 'expires_in' => $session->getExpiresIn(), 41 | 'refresh_token' => $session->refresh_token, 42 | 'refresh_token_expire_in' => $session->getRefreshTokenExpiresIn(), 43 | ]; 44 | } catch (\Throwable $e) { 45 | $transaction->rollBack(); 46 | throw $e; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Form/LogoutForm.php: -------------------------------------------------------------------------------- 1 | getUser(); 14 | if (!$user) { 15 | return; 16 | } 17 | 18 | if ($user instanceof LogoutInterface) { 19 | $user->logout(); 20 | return; 21 | } 22 | 23 | Yii::$app->getUser()->logout(); 24 | } 25 | 26 | private $user; 27 | 28 | /** 29 | * @return null|IdentityInterface 30 | */ 31 | public function getUser() 32 | { 33 | if ($this->user === null) { 34 | $this->user = Yii::$app->getUser()->getIdentity(); 35 | } 36 | 37 | return $this->user; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Form/MyInfoForm.php: -------------------------------------------------------------------------------- 1 | getUser(); 11 | return [ 12 | 'id' => $user->id, 13 | 'username' => $user->username, 14 | 'name' => $user->name, 15 | 'first_name' => $user->first_name, 16 | 'last_name' => $user->last_name, 17 | 'email' => $user->email, 18 | 'avatar' => $user->avatar, 19 | 'language' => $user->language, 20 | 'permissions' => $this->getPermissions(), 21 | ]; 22 | } 23 | 24 | protected function getPermissions() 25 | { 26 | $user = $this->getUser(); 27 | $auth = Yii::$app->getAuthManager(); 28 | $permissions = $auth->getPermissionsByUser($user->id); 29 | $names = array_column($permissions, 'name'); 30 | return $names; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Form/UserForm.php: -------------------------------------------------------------------------------- 1 | user === null) { 25 | $this->user = Yii::$app->getUser()->getIdentity(); 26 | } 27 | 28 | return $this->user; 29 | } 30 | 31 | public function setUser(User $user): void 32 | { 33 | $this->user = $user; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Model/Article.php: -------------------------------------------------------------------------------- 1 | Item::TYPE_PERMISSION] 12 | ], parent::rules()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Model/Role.php: -------------------------------------------------------------------------------- 1 | Item::TYPE_ROLE], 16 | ['type', 'in', 'range' => [Item::TYPE_ROLE]], 17 | ['permission_names', 'required'], 18 | [ 19 | 'permission_names', 'each', 20 | 'rule' => ['exist', 'targetClass' => AuthItem::class, 'targetAttribute' => 'name', 'filter' => ['type' => Item::TYPE_PERMISSION]], 21 | ], 22 | ], parent::rules()); 23 | } 24 | 25 | public function afterSave($insert, $changeAttributes) 26 | { 27 | parent::afterSave($insert, $changeAttributes); 28 | $this->savePermissions(); 29 | } 30 | 31 | private function savePermissions() 32 | { 33 | if (empty($this->permission_names)) { 34 | return; 35 | } 36 | 37 | $auth = Yii::$app->getAuthManager(); 38 | $role = $auth->getRole($this->name); 39 | $auth->removeChildren($role); 40 | foreach ($this->permission_names as $permissionName) { 41 | $auth->addChild($role, $auth->getPermission($permissionName)); 42 | } 43 | } 44 | 45 | public function extraFields() 46 | { 47 | return [ 48 | 'permission_names' => 'permissionNames', 49 | ]; 50 | } 51 | 52 | public function getPermissionNames(): array 53 | { 54 | $permissions = Yii::$app->getAuthManager()->getPermissionsByRole($this->name); 55 | return array_column($permissions, 'name'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Model/Session.php: -------------------------------------------------------------------------------- 1 | ['validateRole']], 17 | ]); 18 | } 19 | 20 | public function validateRole($attribute) 21 | { 22 | $roleName = $this->$attribute; 23 | if (!Yii::$app->getAuthManager()->getRole($roleName)) { 24 | $this->addError($attribute, Yii::t('yii', '{attribute} is invalid.', [ 25 | 'attribute' => sprintf("%s '%s'", $this->getAttributeLabel($attribute), $roleName) 26 | ])); 27 | } 28 | } 29 | 30 | public function afterSave($insert, $changedAttributes) 31 | { 32 | parent::afterSave($insert, $changedAttributes); 33 | 34 | $this->saveRoles(); 35 | } 36 | 37 | private function saveRoles() 38 | { 39 | if (empty($this->role_names)) { 40 | return; 41 | } 42 | 43 | $auth = Yii::$app->getAuthManager(); 44 | // revoke 45 | $auth->revokeAll($this->id); 46 | // assign 47 | foreach ($this->role_names as $name) { 48 | $auth->assign($auth->getRole($name), $this->id); 49 | } 50 | } 51 | 52 | public function extraFields() 53 | { 54 | return [ 55 | 'roles', 56 | 'role_names' => 'roleNames', 57 | ]; 58 | } 59 | 60 | public function getRoles() 61 | { 62 | $roles = Yii::$app->getAuthManager()->getRolesByUser($this->id); 63 | return ArrayHelper::getColumn($roles, function ($element) { 64 | return [ 65 | 'name' => $element->name, 66 | 'description' => $element->description, 67 | ]; 68 | }, false); 69 | } 70 | 71 | public function getRoleNames() 72 | { 73 | $roles = Yii::$app->getAuthManager()->getRolesByUser($this->id); 74 | return array_column($roles, 'name'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Http/Api/Backend/Module.php: -------------------------------------------------------------------------------- 1 | getLike(); 11 | if (!$like) { 12 | return; 13 | } 14 | 15 | // deletes record. 16 | $like->delete(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Api/Form/ArticleLikeForm.php: -------------------------------------------------------------------------------- 1 | getLike(); 13 | if ($like) { 14 | return; 15 | } 16 | 17 | // creates new like record 18 | $like = ArticleLikeFactory::create((int) $this->id, (int) $this->getUser()->getId()); 19 | if (!$like->save()) { 20 | $this->addErrors($like->getErrors()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Api/Form/BaseArticleLikeForm.php: -------------------------------------------------------------------------------- 1 | like === null) { 28 | $this->like = ArticleLike::findByArticleIdAndUserId($this->id, $this->getUser()->getId()); 29 | } 30 | 31 | return $this->like; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Api/Form/UserTrait.php: -------------------------------------------------------------------------------- 1 | user === null) { 17 | $this->user = Yii::$app->getUser()->getIdentity(); 18 | } 19 | 20 | return $this->user; 21 | } 22 | 23 | public function setUser(IdentityInterface $user) 24 | { 25 | $this->user = $user; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Controller/ActiveController.php: -------------------------------------------------------------------------------- 1 | alias('a') 24 | ->orderBy([ 25 | 'a.create_time' => SORT_DESC 26 | ]) 27 | ->andWhere([ 28 | 'a.is_deleted' => 0, 29 | ]); 30 | } 31 | 32 | public function searchModel() 33 | { 34 | return (new DynamicModel(['article_id' => null])) 35 | ->addRule(['article_id'], 'number'); 36 | } 37 | 38 | protected function applyFilter($query, $model, $filter) 39 | { 40 | foreach (['article_id'] as $name) { 41 | if (!empty($model->$name)) { 42 | $query->andWhere(['a.' . $name => (int) $model->$name]); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Controller/ArticleController.php: -------------------------------------------------------------------------------- 1 | alias('a') 27 | ->orderBy([ 28 | 'a.release_time' => SORT_DESC 29 | ]) 30 | ->andWhere([ 31 | 'a.is_deleted' => 0, 32 | 'a.status' => StatusInterface::STATUS_ACTIVE, 33 | ]) 34 | ->andWhere(['<=', 'a.release_time', time()]); 35 | } 36 | 37 | public function searchModel() 38 | { 39 | return (new DynamicModel(['title' => '', 'author' => '', 'summary' => ''])) 40 | ->addRule(['title', 'author', 'summary'], 'string'); 41 | } 42 | 43 | protected function applyFilter($query, $model, $filter) 44 | { 45 | foreach (['title', 'author', 'summary'] as $name) { 46 | if (!empty($model->$name)) { 47 | $query->andWhere(['LIKE', 'a.' . $name, $model->$name]); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Likes an article. 54 | */ 55 | public function actionLike($id) 56 | { 57 | $model = new ArticleLikeForm(); 58 | $model->load(['id' => $id], ''); 59 | return $model->handle(); 60 | } 61 | 62 | /** 63 | * Dislikes an article. 64 | */ 65 | public function actionDislike($id) 66 | { 67 | $model = new ArticleDislikeForm(); 68 | $model->load(['id' => $id], ''); 69 | return $model->handle(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Controller/Controller.php: -------------------------------------------------------------------------------- 1 | OptionsAction::class, 17 | ]; 18 | 19 | return $acitons; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Controller/ControllerTrait.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'class' => Cors::class, 19 | 'cors' => [ 20 | 'Origin' => Yii::$app->params['api.cors.origin'], 21 | 'Access-Control-Request-Method' => Yii::$app->params['api.cors.methods'], 22 | 'Access-Control-Request-Headers' => Yii::$app->params['api.cors.headers'], 23 | 'Access-Control-Allow-Credentials' => Yii::$app->params['api.cors.allowCredentials'], 24 | 'Access-Control-Max-Age' => Yii::$app->params['api.cors.maxAge'], 25 | 'Access-Control-Expose-Headers' => Yii::$app->params['api.cors.exposeHeaders'], 26 | ], 27 | ], 28 | 'authenticator' => [ 29 | 'class' => Authenticator::class, 30 | 'optional' => [ 31 | 'options', 32 | ], 33 | 'authMethods' => [ 34 | 'query' => [ 35 | 'class' => QueryParamAuth::class, 36 | 'tokenParam' => 'access_token', 37 | ], 38 | 'bearer' => [ 39 | 'class' => HttpBearerAuth::class, 40 | ], 41 | ], 42 | ], 43 | 'verb' => [ 44 | 'class' => VerbFilter::class, 45 | 'actions' => $this->verbs(), 46 | ], 47 | 'rateLimiter' => [ 48 | 'class' => RateLimiter::class, 49 | 'redis' => 'redis', 50 | 'capacity' => Yii::$app->params['api.rateLimiter.capacity'], 51 | 'rate' => Yii::$app->params['api.rateLimiter.rate'], 52 | 'limitPeriod' => Yii::$app->params['api.rateLimiter.limitPeriod'], 53 | 'prefix' => Yii::$app->params['api.rateLimiter.prefix'], 54 | 'ttl' => Yii::$app->params['api.rateLimiter.ttl'], 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | protected function verbs() 63 | { 64 | return []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Module.php: -------------------------------------------------------------------------------- 1 | getResponse(); 20 | $response->format = 'json'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Api/Frontend/Module/V1/Controller/ArticleCommentController.php: -------------------------------------------------------------------------------- 1 | function ($model) { 15 | return $model->category->name ?? ''; 16 | }, 17 | 'title', 18 | 'author', 19 | 'cover', 20 | 'status', 21 | 'summary', 22 | 'release_time', 23 | 'create_time', 24 | 'update_time', 25 | ]; 26 | } 27 | 28 | public function extraFields() 29 | { 30 | return [ 31 | 'version', 32 | 'likes_count', 33 | 'content' => function ($model) { 34 | return $model->meta->content ?? ''; 35 | }, 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Api/Model/ArticleComment.php: -------------------------------------------------------------------------------- 1 | getResponse(); 26 | $response->format = 'json'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Asset/AppAsset.php: -------------------------------------------------------------------------------- 1 | getIdentity(); 11 | 12 | return $identity ?? parent::authenticate($user, $request, $response); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Http/Filter/ContentNegotiator.php: -------------------------------------------------------------------------------- 1 | languageParam) || ($language = $request->get($this->languageParam)) === null) { 25 | $language = null; 26 | $this->user = Instance::ensure($this->user, User::class); 27 | if (!$this->user->getIsGuest() && ($identity = $this->user->getIdentity()) instanceof LanguagePickerInterface) { 28 | $language = $identity->getLanguage(); 29 | 30 | $picked = $this->pick($language); 31 | if ($picked !== null) { 32 | return $picked; 33 | } 34 | } 35 | 36 | $language = $request->getCookies()->getValue($this->languageParam, null); 37 | $picked = $this->pick($language); 38 | if ($picked !== null) { 39 | return $picked; 40 | } 41 | } 42 | 43 | return parent::negotiateLanguage($request); 44 | } 45 | 46 | /** 47 | * Pick language. 48 | * 49 | * @param string $language 50 | * 51 | * @return string|null returns a supportted language, returns null if not supported. 52 | */ 53 | protected function pick($language) 54 | { 55 | if ($language === null) { 56 | return null; 57 | } 58 | 59 | if (isset($this->languages[$language])) { 60 | return $this->languages[$language]; 61 | } 62 | foreach ($this->languages as $key => $supported) { 63 | if (is_int($key) && $this->isLanguageSupported($language, $supported)) { 64 | return $supported; 65 | } 66 | } 67 | 68 | return null; 69 | } 70 | 71 | /** 72 | * Store language to cookie. 73 | * 74 | * @param string $lang 75 | */ 76 | public function storeLanguageToCookie($lang) 77 | { 78 | $cookie = new Cookie(['name' => $this->languageParam, 'value' => $lang]); 79 | Yii::$app->getResponse()->getCookies()->add($cookie); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/Http/Filter/LanguagePickerInterface.php: -------------------------------------------------------------------------------- 1 | Yii::t('app', 'Name'), 40 | 'email' => Yii::t('app', 'Email'), 41 | 'subject' => Yii::t('app', 'Subject'), 42 | 'body' => Yii::t('app', 'Content'), 43 | 'verifyCode' => Yii::t('app', 'Verification Code'), 44 | ]; 45 | } 46 | 47 | /** 48 | * Sends an email to the specified email address using the information collected by this model. 49 | * 50 | * @param string $email the target email address 51 | * @return bool whether the email was sent 52 | */ 53 | public function sendEmail($email) 54 | { 55 | return Yii::$app->getMailer() 56 | ->compose() 57 | ->setTo($email) 58 | ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name]) 59 | ->setReplyTo([$this->email => $this->name]) 60 | ->setSubject($this->subject) 61 | ->setTextBody($this->body) 62 | ->send(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Http/Form/PasswordResetRequestForm.php: -------------------------------------------------------------------------------- 1 | User::class, 27 | 'filter' => ['status' => User::STATUS_ACTIVE], 28 | 'message' => 'There is no user with this email address.' 29 | ], 30 | ]; 31 | } 32 | 33 | /** 34 | * Sends an email with a link, for resetting the password. 35 | * 36 | * @return bool whether the email was send 37 | */ 38 | public function sendEmail() 39 | { 40 | /* @var $user User */ 41 | $user = User::findOne([ 42 | 'status' => User::STATUS_ACTIVE, 43 | 'email' => $this->email, 44 | ]); 45 | 46 | if (!$user) { 47 | return false; 48 | } 49 | 50 | if (!User::isPasswordResetTokenValid($user->password_reset_token)) { 51 | $user->generatePasswordResetToken(); 52 | if (!$user->save()) { 53 | return false; 54 | } 55 | } 56 | 57 | return Yii::$app 58 | ->mailer 59 | ->compose( 60 | ['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'], 61 | ['user' => $user] 62 | ) 63 | ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name . ' robot']) 64 | ->setTo($this->email) 65 | ->setSubject('Password reset for ' . Yii::$app->name) 66 | ->send(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/Http/Form/ResendVerificationEmailForm.php: -------------------------------------------------------------------------------- 1 | getUser(); 32 | if (!$user) { 33 | $this->addError($attribute, 'There is no user with this email address.'); 34 | return; 35 | } 36 | if ($user->isActive()) { 37 | $this->addError($attribute, 'The email was verified.'); 38 | return; 39 | } 40 | } 41 | 42 | /** 43 | * Sends confirmation email to user 44 | * 45 | * @return bool whether the email was sent 46 | */ 47 | public function sendEmail() 48 | { 49 | $user = User::findOne([ 50 | 'email' => $this->email, 51 | ]); 52 | 53 | if ($user === null) { 54 | return false; 55 | } 56 | 57 | return Yii::$app 58 | ->mailer 59 | ->compose( 60 | ['html' => 'emailVerify-html', 'text' => 'emailVerify-text'], 61 | ['user' => $user] 62 | ) 63 | ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name . ' robot']) 64 | ->setTo($this->email) 65 | ->setSubject('Account registration at ' . Yii::$app->name) 66 | ->send(); 67 | } 68 | 69 | private $user; 70 | 71 | public function getUser(): ?User 72 | { 73 | if ($this->user === null) { 74 | $this->user = User::findOne(['email' => $this->email]); 75 | } 76 | 77 | return $this->user; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/Http/Form/ResetPasswordForm.php: -------------------------------------------------------------------------------- 1 | _user = User::findByPasswordResetToken($token); 34 | if (!$this->_user) { 35 | throw new InvalidArgumentException('Wrong password reset token.'); 36 | } 37 | parent::__construct($config); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function rules() 44 | { 45 | return [ 46 | ['password', 'required'], 47 | ['password', 'string', 'min' => 6], 48 | ]; 49 | } 50 | 51 | /** 52 | * Resets password. 53 | * 54 | * @return bool if password was reset. 55 | */ 56 | public function resetPassword() 57 | { 58 | $user = $this->_user; 59 | $user->setPassword($this->password); 60 | $user->removePasswordResetToken(); 61 | 62 | return $user->save(false); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Http/Form/UploadForm.php: -------------------------------------------------------------------------------- 1 | upload(); 18 | return [ 19 | 'url' => $url, 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Form/VerifyEmailForm.php: -------------------------------------------------------------------------------- 1 | user = User::findByVerificationToken($token); 34 | if (!$this->user) { 35 | throw new InvalidArgumentException('Wrong verify email token.'); 36 | } 37 | parent::__construct($config); 38 | } 39 | 40 | /** 41 | * Verify email 42 | * 43 | * @return User|null the saved model or null if saving fails 44 | */ 45 | public function verifyEmail() 46 | { 47 | $user = $this->user; 48 | $user->status = User::STATUS_ACTIVE; 49 | return $user->save(false) ? $user : null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Rest/Controller.php: -------------------------------------------------------------------------------- 1 | safeRun($id, [$this, 'delete']); 18 | } 19 | 20 | public function delete($id) 21 | { 22 | $model = $this->findModel($id); 23 | 24 | if ($this->checkAccess) { 25 | call_user_func($this->checkAccess, $this->id, $model); 26 | } 27 | 28 | if ($this->deleteModel($model) === false) { 29 | throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); 30 | } 31 | 32 | Yii::$app->getResponse()->setStatusCode(204); 33 | } 34 | 35 | /** 36 | * @param ActiveRecord $model 37 | * @return int|false 38 | */ 39 | private function deleteModel($model) 40 | { 41 | if ($model instanceof SoftDeleteInterface) { 42 | return $model->softDelete(); 43 | } 44 | 45 | return $model->delete(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Rest/OptionsAction.php: -------------------------------------------------------------------------------- 1 | getResponse()->setStatusCode($this->statusCode); 17 | 18 | parent::run($id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Rest/SafeActionTrait.php: -------------------------------------------------------------------------------- 1 | enableTransaction) { 21 | return call_user_func_array($run, $args); 22 | } 23 | 24 | return DbHelper::transaction($run, $args, $this->getDb()); 25 | } 26 | 27 | public function getDb(): Connection 28 | { 29 | /** @var ActiveRecord $modelClass */ 30 | $modelClass = $this->modelClass; 31 | /** @var Connection $db */ 32 | return $modelClass::getDb(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Rest/Serializer.php: -------------------------------------------------------------------------------- 1 | linksEnvelope => Link::serialize($pagination->getLinks(true)), 20 | $this->metaEnvelope => [ 21 | 'limit' => $pagination->getPageSize(), 22 | 'page' => $pagination->getPage() + 1, 23 | 'page_count' => $pagination->getPageCount(), 24 | 'total_count' => $pagination->totalCount, 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Rest/UpdateAction.php: -------------------------------------------------------------------------------- 1 | session->setFlash('error', 'This is the message'); 12 | * Yii::$app->session->setFlash('success', 'This is the message'); 13 | * Yii::$app->session->setFlash('info', 'This is the message'); 14 | * ``` 15 | * 16 | * Multiple messages could be set as follows: 17 | * 18 | * ```php 19 | * Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']); 20 | * ``` 21 | * 22 | * @author Kartik Visweswaran 23 | * @author Alexander Makarov 24 | */ 25 | class Alert extends \yii\bootstrap\Widget 26 | { 27 | /** 28 | * @var array the alert types configuration for the flash messages. 29 | * This array is setup as $key => $value, where: 30 | * - key: the name of the session flash variable 31 | * - value: the bootstrap alert type (i.e. danger, success, info, warning) 32 | */ 33 | public $alertTypes = [ 34 | 'error' => 'alert-danger', 35 | 'danger' => 'alert-danger', 36 | 'success' => 'alert-success', 37 | 'info' => 'alert-info', 38 | 'warning' => 'alert-warning' 39 | ]; 40 | /** 41 | * @var array the options for rendering the close button tag. 42 | * Array will be passed to [[\yii\bootstrap\Alert::closeButton]]. 43 | */ 44 | public $closeButton = []; 45 | 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function run() 51 | { 52 | $session = Yii::$app->session; 53 | $flashes = $session->getAllFlashes(); 54 | $appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 55 | 56 | foreach ($flashes as $type => $flash) { 57 | if (!isset($this->alertTypes[$type])) { 58 | continue; 59 | } 60 | 61 | foreach ((array) $flash as $i => $message) { 62 | echo \yii\bootstrap\Alert::widget([ 63 | 'body' => $message, 64 | 'closeButton' => $this->closeButton, 65 | 'options' => array_merge($this->options, [ 66 | 'id' => $this->getId() . '-' . $type . '-' . $i, 67 | 'class' => $this->alertTypes[$type] . $appendClass, 68 | ]), 69 | ]); 70 | } 71 | 72 | $session->removeFlash($type); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Job/MailJob.php: -------------------------------------------------------------------------------- 1 | mailer = Instance::ensure($this->mailer, MailerInterface::class); 30 | foreach ($this->messages as &$message) { 31 | $message = Instance::ensure($message, MessageInterface::class); 32 | } 33 | unset($message); 34 | } 35 | 36 | protected function run() 37 | { 38 | $sent = $this->mailer->sendMultiple($this->messages); 39 | return $sent; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Model/ActiveRecord.php: -------------------------------------------------------------------------------- 1 | 32], 46 | ]; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function attributeLabels() 53 | { 54 | return [ 55 | 'id' => Yii::t('app', 'ID'), 56 | 'creator' => Yii::t('app', 'Creator'), 57 | 'name' => Yii::t('app', 'Name'), 58 | 'create_time' => Yii::t('app', 'Create Time'), 59 | 'update_time' => Yii::t('app', 'Update Time'), 60 | ]; 61 | } 62 | 63 | public function fields() 64 | { 65 | return [ 66 | 'id', 67 | 'name', 68 | 'create_time', 69 | 'update_time', 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Model/ArticleLike.php: -------------------------------------------------------------------------------- 1 | TimestampBehavior::class, 34 | 'updatedAtAttribute' => null, 35 | ], 36 | [ 37 | 'class' => CreatorBehavior::class, 38 | 'creatorAttribute' => 'user_id', 39 | ], 40 | ]; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function rules() 47 | { 48 | return [ 49 | [['article_id'], 'required'], 50 | [['article_id', 'create_time'], 'integer'], 51 | // [['article_id', 'user_id'], 'unique', 'targetAttribute' => ['article_id', 'user_id']], 52 | ['article_id', 'exist', 'targetClass' => Article::class, 'targetAttribute' => 'id'], 53 | ]; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function attributeLabels() 60 | { 61 | return [ 62 | 'article_id' => Yii::t('app', 'Article ID'), 63 | 'user_id' => Yii::t('app', 'User ID'), 64 | 'create_time' => Yii::t('app', 'Create Time'), 65 | ]; 66 | } 67 | 68 | public function getArticle() 69 | { 70 | return $this->hasOne(Article::class, ['id' => 'article_id']); 71 | } 72 | 73 | public function getUser() 74 | { 75 | return $this->hasOne(User::class, ['id' => 'user_id']); 76 | } 77 | 78 | public static function findByArticleIdAndUserId(int $articleId, int $userId): ?self 79 | { 80 | return static::findOne([ 81 | 'article_id' => $articleId, 82 | 'user_id' => $userId, 83 | ]); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/Model/ArticleMeta.php: -------------------------------------------------------------------------------- 1 | Yii::t('app', 'Article ID'), 44 | 'content' => Yii::t('app', 'Content'), 45 | ]; 46 | } 47 | 48 | public function getArticle() 49 | { 50 | return $this->hasOne(Article::class, ['id' => 'article_id']); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Model/SoftDeleteInterface.php: -------------------------------------------------------------------------------- 1 | updateAttributes([ 13 | $this->softDeleteField => $this->softDeleteValue, 14 | ]); 15 | } 16 | 17 | /** 18 | * @return bool 19 | */ 20 | public function isDeleted(): bool 21 | { 22 | $field = $this->softDeleteField; 23 | return $this->$field == $this->softDeleteValue; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Model/StatusInterface.php: -------------------------------------------------------------------------------- 1 | status === StatusInterface::STATUS_ACTIVE; 9 | } 10 | 11 | public function isDisabled(): bool 12 | { 13 | return $this->status === StatusInterface::STATUS_DISABLED; 14 | } 15 | 16 | public function getStatusName(): string 17 | { 18 | switch ($this->status) { 19 | case StatusInterface::STATUS_ACTIVE: 20 | return \Yii::t('app', 'Active'); 21 | case StatusInterface::STATUS_DISABLED: 22 | return \Yii::t('app', 'Disable'); 23 | default: 24 | return \Yii::t('app', 'Unknown'); 25 | } 26 | 27 | return 'Unknown'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Queue/Gii/Generator.php: -------------------------------------------------------------------------------- 1 | '@resources/queue/gii/views', 18 | ]; 19 | 20 | public $formView = '@resources/queue/gii/views/form.php'; 21 | 22 | public function formView() 23 | { 24 | return $this->formView; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Queue/Job.php: -------------------------------------------------------------------------------- 1 | queue; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function execute($queue) 29 | { 30 | $this->queue = $queue; 31 | 32 | if (!$this->beforeRun()) { 33 | throw new JobPreventedException(); 34 | } 35 | 36 | $result = $this->run(); 37 | $this->afterRun($result); 38 | } 39 | 40 | /** 41 | * Invoke before running job. 42 | * 43 | * @return bool whether the job is valid. 44 | */ 45 | protected function beforeRun(): bool 46 | { 47 | $event = new JobEvent(); 48 | $this->trigger(JobEvent::BEFORE_RUN, $event); 49 | return $event->isValid; 50 | } 51 | 52 | /** 53 | * Invoke after finishing job. 54 | */ 55 | protected function afterRun($result) 56 | { 57 | $event = new JobEvent([ 58 | 'result' => $result 59 | ]); 60 | $this->trigger(JobEvent::AFTER_RUN, $event); 61 | } 62 | 63 | /** 64 | * Run job. 65 | * 66 | * @return mixed job result. 67 | */ 68 | abstract protected function run(); 69 | } 70 | -------------------------------------------------------------------------------- /app/Queue/JobEvent.php: -------------------------------------------------------------------------------- 1 | getMaxAttemptTimes(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Rbac/Rule/ArticleDeleteRule.php: -------------------------------------------------------------------------------- 1 | user_id : false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Rbac/Rule/RoleDeleteRule.php: -------------------------------------------------------------------------------- 1 | getAuthManager(); 14 | $roles = $auth->getRolesByUser($user); 15 | $roleName = $params['model']->name; 16 | return isset($roles[$roleName]) ? false : true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Rbac/Rule/Rule.php: -------------------------------------------------------------------------------- 1 | id == $user) { 18 | throw new ForbiddenHttpException(Yii::t('app', 'You cannot delete your own account')); 19 | } 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Trait/.gitkeep: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /app/Validator/PasswordValidator.php: -------------------------------------------------------------------------------- 1 | message === null) { 30 | $this->message = Yii::t('yii', '{attribute} is not a valid URL.'); 31 | } 32 | } 33 | 34 | protected function validateValue($value) 35 | { 36 | $components = parse_url($value); 37 | foreach ($this->components as $name) { 38 | if (!isset($components[$name])) { 39 | return [$this->message, []]; 40 | } 41 | } 42 | 43 | if (!in_array($components[self::COMPONENT_SCHEME], $this->schemes)) { 44 | return [$this->message, []]; 45 | } 46 | 47 | return null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bin/init.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line init script for Windows. 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set YII_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%YII_PATH%init" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /bin/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | exit($exitCode); 22 | -------------------------------------------------------------------------------- /bin/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set YII_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | tests: tests 3 | output: tests/_output 4 | data: tests/_data 5 | support: tests/_support 6 | envs: tests/_envs 7 | actor_suffix: Tester 8 | bootstrap: _bootstrap.php 9 | settings: 10 | colors: true 11 | memory_limit: 1024M 12 | extensions: 13 | enabled: 14 | - Codeception\Extension\RunFailed 15 | coverage: 16 | enabled: true 17 | remote: false 18 | include: 19 | - app/* 20 | exclude: 21 | - app/Http/Controller/* 22 | - app/Http/Api/Controller/* 23 | - app/Http/Api/Backend/Controller/* 24 | - app/Http/Api/Backend/Module/V1/Controller/* 25 | - app/Http/Api/Frontend/Controller/* 26 | - app/Http/Api/Frontend/Module/V1/Controller/* 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "razonyang/yii2-app-template", 3 | "description": "Yii2 App Project Template", 4 | "keywords": ["yii2", "framework", "advanced", "project template"], 5 | "homepage": "https://github.com/razonyang/yii2-app-template/issues", 6 | "type": "project", 7 | "license": "BSD-3-Clause", 8 | "support": { 9 | "issues": "https://github.com/razonyang/yii2-app-template/issues?state=open", 10 | "forum": "http://www.yiiframework.com/forum/", 11 | "wiki": "http://www.yiiframework.com/wiki/", 12 | "irc": "irc://irc.freenode.net/yii", 13 | "source": "https://github.com/yiisoft/yii2" 14 | }, 15 | "minimum-stability": "dev", 16 | "prefer-stable": true, 17 | "require": { 18 | "php": ">=7.1", 19 | "ext-mbstring": "*", 20 | "ramsey/uuid": "^3.8", 21 | "razonyang/yii2-jsend": "^1.0.1", 22 | "razonyang/yii2-log-target-db": "^1.0", 23 | "razonyang/yii2-rate-limiter": "^1.0", 24 | "razonyang/yii2-setting": "^1.0", 25 | "razonyang/yii2-uploader": "^1.0", 26 | "yiisoft/yii2": "~2.0.23", 27 | "yiisoft/yii2-bootstrap": "~2.0.0", 28 | "yiisoft/yii2-queue": "^2.2", 29 | "yiisoft/yii2-redis": "^2.0", 30 | "yiisoft/yii2-swiftmailer": "~2.1.0" 31 | }, 32 | "require-dev": { 33 | "codeception/codeception": "^3.0", 34 | "codeception/verify": "^1.1", 35 | "phpunit/phpunit": "^7", 36 | "facebook/webdriver": "^1.7", 37 | "symfony/browser-kit": ">=2.7 <=4.2.4", 38 | "yiisoft/yii2-debug": "~2.1.0", 39 | "yiisoft/yii2-gii": "~2.1.0" 40 | }, 41 | "config": { 42 | "process-timeout": 1800, 43 | "fxp-asset": { 44 | "enabled": false 45 | } 46 | }, 47 | "repositories": [ 48 | { 49 | "type": "composer", 50 | "url": "https://asset-packagist.org" 51 | } 52 | ], 53 | "autoload": { 54 | "psr-4": { 55 | "App\\": "app/" 56 | } 57 | }, 58 | "autoload-dev": { 59 | "psr-4": { 60 | "App\\Tests\\": "tests" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /conf/app.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=App Docker Compose Service 3 | Requires=docker.service 4 | After=docker.service 5 | 6 | [Service] 7 | Restart=always 8 | 9 | WorkingDirectory=/app 10 | 11 | # Remove old containers, images and volumes 12 | ExecStartPre=/usr/bin/docker-compose down -v 13 | ExecStartPre=/usr/bin/docker-compose rm -v 14 | ExecStartPre=-/bin/bash -c 'docker volume rm $(docker volume ls -q)' 15 | ExecStartPre=-/bin/bash -c 'docker rmi $(docker images | grep "" | awk \'{print $3}\')' 16 | ExecStartPre=-/bin/bash -c 'docker rm -v $(docker ps -aq)' 17 | 18 | # Compose up 19 | ExecStart=/usr/bin/docker-compose up --scale queue=5 20 | 21 | # Compose down, remove containers and volumes 22 | ExecStop=/usr/bin/docker-compose down -v 23 | 24 | [Install] 25 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /conf/crontab: -------------------------------------------------------------------------------- 1 | # crond jobs 2 | 3 | * * * * * /usr/local/bin/php /app/bin/yii hello # for testing 4 | 5 | 0 0 * * * /usr/bin/php /app/bin/yii gc/session # flush expired sessions 6 | -------------------------------------------------------------------------------- /conf/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | charset utf-8; 3 | client_max_body_size 128M; 4 | sendfile off; 5 | 6 | listen 80; ## listen for ipv4 7 | # listen [::]:80 default_server ipv6only=on; ## listen for ipv6 8 | 9 | server_name yii.test; 10 | root /app/public/; 11 | index index.php; 12 | 13 | access_log /app/runtime/logs/app-access.log; 14 | error_log /app/runtime/logs/app-error.log; 15 | 16 | location / { 17 | try_files $uri $uri/ /index.php$is_args$args; 18 | } 19 | 20 | # uncomment to avoid processing of calls to non-existing static files by Yii 21 | location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { 22 | try_files $uri =404; 23 | } 24 | error_page 404 /404.html; 25 | 26 | location ~ \.php$ { 27 | include fastcgi_params; 28 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 29 | fastcgi_pass php-fpm; 30 | try_files $uri =404; 31 | } 32 | 33 | location ~ /\.(ht|svn|git) { 34 | deny all; 35 | } 36 | } -------------------------------------------------------------------------------- /conf/queue.service: -------------------------------------------------------------------------------- 1 | # systemd service unit 2 | 3 | [Unit] 4 | Description=App Queue Worker %I 5 | After=network.target 6 | # the following two lines only apply if your queue backend is mysql 7 | # replace this with the service that powers your backend 8 | After=mysql.service 9 | Requires=mysql.service 10 | 11 | [Service] 12 | #User=nginx 13 | #Group=nginx 14 | ExecStart=/usr/bin/php /app/bin/yii queue/listen --verbose 15 | Restart=on-failure 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | *-local.php 2 | *-codeception.php -------------------------------------------------------------------------------- /config/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'app-console', 5 | 'bootstrap' => ['log', 'queue'], 6 | 'controllerNamespace' => 'App\Console\Controller', 7 | 'aliases' => [ 8 | '@bower' => '@vendor/bower-asset', 9 | '@npm' => '@vendor/npm-asset', 10 | ], 11 | 'controllerMap' => [ 12 | 'migrate' => [ 13 | 'class' => \yii\console\controllers\MigrateController::class, 14 | 'templateFile' => '@resources/console/views/migration.php', 15 | 'migrationPath' => [ 16 | '@resources/migrations', 17 | '@yii/rbac/migrations', 18 | '@yii/log/migrations/', 19 | ], 20 | 'migrationNamespaces' => [ 21 | 'RazonYang\Yii2\Log\Db\Migration', 22 | 'RazonYang\Yii2\Setting\Migration', 23 | ], 24 | ], 25 | 'serve' => [ 26 | 'class' => \yii\console\controllers\ServeController::class, 27 | 'docroot' => '@webroot', 28 | 'port' => 8080, 29 | ], 30 | ], 31 | 'components' => [ 32 | 'log' => [ 33 | 'traceLevel' => YII_DEBUG ? 3 : 0, 34 | 'targets' => [ 35 | 'file' => [ 36 | 'class' => yii\log\FileTarget::class, 37 | 'logFile' => '@runtime/logs/console.log', 38 | 'levels' => ['error', 'warning'], 39 | ], 40 | ], 41 | ], 42 | ], 43 | ]; 44 | -------------------------------------------------------------------------------- /config/debug.php: -------------------------------------------------------------------------------- 1 | \yii\debug\Module::class, 5 | 'panels' => [ 6 | 'queue' => \yii\queue\debug\Panel::class, 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /config/gii.php: -------------------------------------------------------------------------------- 1 | \yii\gii\Module::class, 5 | 'generators' => [ 6 | 'model' => [ 7 | 'class' => \yii\gii\generators\model\Generator::class, 8 | 'ns' => 'App\Model', 9 | 'baseClass' => 'App\Model\ActiveRecord', 10 | 'useTablePrefix' => true, 11 | 'generateLabelsFromComments' => true, 12 | 'queryNs' => 'App\Model', 13 | 'singularize' => true, 14 | 'standardizeCapitals' => true, 15 | 'enableI18N' => true, 16 | 'queryBaseClass' => \App\Db\ActiveQuery::class, 17 | ], 18 | 'module' => [ 19 | 'class' => \App\Gii\Generator\Module\Generator::class, 20 | ], 21 | 'job' => [ 22 | 'class' => \App\Queue\Gii\Generator::class, 23 | ], 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 6 | 'supportEmail' => 'support@example.com', 7 | 8 | 'site.since' => 2008, 9 | 10 | // user 11 | 'user.session.duration' => 7200, // session's duration in seconds, default to 2 hours. 12 | 'user.session.refreshTokenDuration' => 403200, // refresh token's duration in seconds, default to a week. 13 | 'user.session.durationAfterRefresh' => 300, // old session's remaining durantion after refreshing session, default to 5 minutes. 14 | 'user.passwordResetTokenExpire' => 7200, 15 | 16 | /* ============================= backend ============================== */ 17 | 'backend.url' => 'http://localhost', 18 | 19 | /* =============================== API =============================== */ 20 | // CORS 21 | 'api.cors.origin' => ['*'], 22 | 'api.cors.methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], 23 | 'api.cors.headers' => ['Content-Type', 'Authorization'], 24 | 'api.cors.allowCredentials' => null, 25 | 'api.cors.maxAge' => 86400, 26 | 'api.cors.exposeHeaders' => [], 27 | 28 | // rate limiter 29 | 'api.rateLimiter.capacity' => 5000, 30 | 'api.rateLimiter.rate' => 0.72, 31 | 'api.rateLimiter.limitPeriod' => 3600, 32 | 'api.rateLimiter.ttl' => 3600, 33 | 'api.rateLimiter.prefix' => 'rate_limiter:', 34 | ]; 35 | -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | 'site/index', 5 | 'GET /captcha' => 'site/captcha', 6 | 'GET,POST /contact' => 'site/contact', 7 | 'GET,POST /login' => 'site/login', 8 | 'POST /logout' => 'site/logout', 9 | 'GET,POST /signup' => 'site/signup', 10 | 'GET,POST /reset-password' => 'site/reset-password', 11 | 'GET,POST /request-password-reset' => 'site/request-password-reset', 12 | 'GET /verify-email' => 'site/verify-email', 13 | 'GET,POST /resend-verification-email' => 'site/resend-verification-email', 14 | 'GET /about' => 'site/about', 15 | 16 | 'GET /articles' => 'article/index', 17 | 'GET /articles/' => 'article/view', 18 | 'GET /articles//comments' => 'article/comments', 19 | 20 | // API 21 | [ 22 | 'class' => \yii\web\GroupUrlRule::class, 23 | 'prefix' => 'api/backend/v1', 24 | 'rules' => [ 25 | 'POST upload/' => 'upload/', 26 | 'POST ' => 'user/', 27 | 'PUT access-token' => 'user/refresh-access-token', 28 | 'OPTIONS ' => 'user/options', 29 | 30 | 'GET my/info' => 'my/info', 31 | 'PUT my/password' => 'my/password', 32 | 33 | 'POST upload/' => 'upload/', 34 | 'OPTIONS upload/' => 'upload/options', 35 | ], 36 | ], 37 | [ 38 | 'class' => \yii\rest\UrlRule::class, 39 | 'controller' => [ 40 | // backend 41 | 'api/backend/v1/user', 42 | 'api/backend/v1/article', 43 | 'api/backend/v1/article-category', 44 | 45 | // frontend 46 | 'api/frontend/v1/article-comment', 47 | ], 48 | ], 49 | 50 | [ 51 | 'class' => \yii\rest\UrlRule::class, 52 | 'controller' => [ 53 | 'api/backend/v1/permission', 54 | 'api/backend/v1/role', 55 | 'api/backend/v1/my/sessions' => 'api/backend/v1/my-session', 56 | 'api/backend/v1/setting', 57 | ], 58 | 'tokens' => [ 59 | '{id}' => '', 60 | ], 61 | ], 62 | 63 | [ 64 | 'class' => \yii\rest\UrlRule::class, 65 | 'controller' => [ 66 | 'api/frontend/v1/article', 67 | ], 68 | 'extraPatterns' => [ 69 | 'PUT {id}/likes' => 'like', 70 | 'DELETE {id}/likes' => 'dislike', 71 | ], 72 | ], 73 | ]; 74 | -------------------------------------------------------------------------------- /config/test.php: -------------------------------------------------------------------------------- 1 | 'app-tests', 5 | 'components' => [ 6 | 'db' => [ 7 | 'dsn' => 'mysql:host=localhost;dbname=yiitest', 8 | ], 9 | 'log' => [ 10 | 'traceLevel' => 3, 11 | 'targets' => [ 12 | 'file' => [ 13 | 'class' => yii\log\FileTarget::class, 14 | 'logFile' => '@runtime/logs/test.log', 15 | 'levels' => ['error', 'warning', 'info', 'trace'], 16 | ], 17 | ], 18 | ], 19 | ], 20 | ]; 21 | -------------------------------------------------------------------------------- /docs/en/DOCKER.md: -------------------------------------------------------------------------------- 1 | INSTALLATION 2 | ------------ 3 | 4 | ``` 5 | $ git clone https://github.com/razonyang/yii2-app-template app 6 | $ cd app 7 | ``` 8 | 9 | INITIALIZE 10 | ---------- 11 | 12 | ``` 13 | $ cd app 14 | 15 | $ docker-compose start php-fpm 16 | 17 | $ docker-compose exec php-fpm php -r "file_put_contents('/app/composer.phar', file_get_contents('https://getcomposer.org/composer.phar'));" 18 | 19 | $ docker-compose exec php-fpm php /app/bin/init --env=Docker 20 | 21 | $ docker-compose exec php-fpm php /app/composer.phar --working-dir=/app install 22 | 23 | $ docker-compose up --scale queue=5 24 | ``` 25 | 26 | - `./bin/init --env=Docker` for initializing docker configurations 27 | - `docker-compose up` for starting docker services, add `-d` to run as daemon, `--scale queue=5` starts `5` queue workers. 28 | 29 | 30 | DATABASE MIGRATION 31 | ------------------ 32 | 33 | ```shell 34 | $ docker-compose exec php-fpm /app/bin/yii migrate 35 | ``` 36 | 37 | WEB 38 | --- 39 | 40 | ```shell 41 | $ curl -XPOST http://localhost:8080/api/backend/v1/login -H "Content-Type: application/json" 42 | {"status":"fail","data":{"username":"Username cannot be blank.","password":"Password cannot be blank."}} 43 | 44 | $ curl -XPOST http://localhost:8080/api/backend/v1/login -H "Content-Type: application/json" -d '{"username": "Admin", "password": "123456"}' 45 | {"status":"success","data":{"id":1,"username":"Admin","email":"admin@example.com","token":"1YG-bENSZ7uQWWUVcytBZ-Vr7uHikT5I","expires_in":7200,"refresh_token":"1O3LL5FlVUBtdsKh2Dgj_r83jgmEbOCVtC1oAObpIhKs8QIpqdPV66t1SCywfL8k","refresh_token_expire_in":403200}} 46 | ``` 47 | 48 | Open [http://localhost:8080](http://localhost:8080) to take a look of the front end. 49 | 50 | CONSOLE 51 | ------- 52 | 53 | ```shell 54 | $ docker-compose exec php-fpm /app/bin/yii hello 55 | Hello World 56 | ``` 57 | 58 | QUEUE 59 | ----- 60 | 61 | ```shell 62 | $ docker-compose exec php-fpm /app/bin/yii hello/job 63 | App\Console\Job\HelloJob#1 64 | ``` 65 | 66 | CRON 67 | ---- 68 | 69 | You should add your cron jobs into `resources/docker/cron/crontab`, and restart cron service `docker-compose restart cron`. 70 | 71 | TESTS 72 | ----- 73 | 74 | You should create test database(default to `yiitest`) and apply migrations into it. 75 | 76 | ```shell 77 | $ docker-compose exec db mysql -uroot -p 78 | $ docker-compose exec php-fpm /app/bin/yii_test migrate 79 | ``` 80 | 81 | ```shell 82 | $ docker-compose exec php-fpm bash -c "cd /app/ && ./vendor/bin/codecept run --coverage --coverage-xml" 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/en/INSTALLATION.md: -------------------------------------------------------------------------------- 1 | INSTALLATION 2 | ------------ 3 | 4 | ``` 5 | $ composer create-project --prefer-dist razonyang/yii2-app-template app 6 | ``` 7 | 8 | or 9 | 10 | ``` 11 | $ git clone https://github.com/razonyang/yii2-app-template app 12 | $ cd app 13 | $ composer install 14 | ``` 15 | 16 | INITIALIZE 17 | ---------- 18 | 19 | ```shell 20 | $ cd app 21 | 22 | $ ./bin/init 23 | 24 | $ ./bin/yii migrate 25 | ``` 26 | 27 | - `./bin/init` for initializing 28 | - `./bin/yii migrate` for database migrations 29 | 30 | > You should modify configurations located in `config` before `migrate`. 31 | 32 | WEB 33 | --- 34 | 35 | ```shell 36 | $ ./bin/yii serve 37 | 38 | $ curl -XPOST http://localhost:8080/api/backend/v1/login -H "Content-Type: application/json" 39 | {"status":"fail","data":{"username":"Username cannot be blank.","password":"Password cannot be blank."}} 40 | 41 | $ curl -XPOST http://localhost:8080/api/backend/v1/login -H "Content-Type: application/json" -d '{"username": "Admin", "password": "123456"}' 42 | {"status":"success","data":{"id":1,"username":"Admin","email":"admin@example.com","token":"1YG-bENSZ7uQWWUVcytBZ-Vr7uHikT5I","expires_in":7200,"refresh_token":"1O3LL5FlVUBtdsKh2Dgj_r83jgmEbOCVtC1oAObpIhKs8QIpqdPV66t1SCywfL8k","refresh_token_expire_in":403200}} 43 | ``` 44 | 45 | Open [http://localhost:8080](http://localhost:8080) to take a look of the front end. 46 | 47 | CONSOLE 48 | ------- 49 | 50 | ``` 51 | $ ./bin/yii hello 52 | Hello World 53 | ``` 54 | 55 | QUEUE 56 | ----- 57 | 58 | ```shell 59 | $ ./bin/yii hello/job 60 | App\Console\Job\HelloJob#1 61 | 62 | $ ./bin/yii queue/listen -v 63 | 2019-08-06 12:30:28 [pid: 26514] - Worker is started 64 | 2019-08-06 12:30:51 [1] App\Console\Job\HelloJob (attempt: 1, pid: 26514) - Started 65 | Hello World 66 | 2019-08-06 12:30:51 [1] App\Console\Job\HelloJob (attempt: 1, pid: 26514) - Done (0.081 s) 67 | ``` 68 | 69 | TESTS 70 | ----- 71 | 72 | You should create test database(default to `yiitest`) and apply migrations into it. 73 | 74 | ```shell 75 | $ ./bin/yii_test migrate 76 | ``` 77 | 78 | ```shell 79 | $ ./vendor/bin/codecept run --coverage --coverage-xml 80 | ``` 81 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | 3 | # prevent httpd from serving dotfiles (.htaccess, .svn, .git, etc.) 4 | RedirectMatch 403 /\..*$ 5 | # if a directory or a file exists, use it directly 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteCond %{REQUEST_FILENAME} !-d 8 | # otherwise forward it to index.php 9 | RewriteRule . index.php -------------------------------------------------------------------------------- /public/css/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | .wrap { 7 | min-height: 100%; 8 | height: auto; 9 | margin: 0 auto -60px; 10 | padding: 0 0 60px; 11 | } 12 | 13 | .wrap > .container { 14 | padding: 70px 15px 20px; 15 | } 16 | 17 | .footer { 18 | height: 60px; 19 | background-color: #f5f5f5; 20 | border-top: 1px solid #ddd; 21 | padding-top: 20px; 22 | } 23 | 24 | .jumbotron { 25 | text-align: center; 26 | background-color: transparent; 27 | } 28 | 29 | .jumbotron .btn { 30 | font-size: 21px; 31 | padding: 14px 24px; 32 | } 33 | 34 | .not-set { 35 | color: #c55; 36 | font-style: italic; 37 | } 38 | 39 | /* add sorting icons to gridview sort links */ 40 | a.asc:after, a.desc:after { 41 | position: relative; 42 | top: 1px; 43 | display: inline-block; 44 | font-family: 'Glyphicons Halflings'; 45 | font-style: normal; 46 | font-weight: normal; 47 | line-height: 1; 48 | padding-left: 5px; 49 | } 50 | 51 | a.asc:after { 52 | content: "\e151"; 53 | } 54 | 55 | a.desc:after { 56 | content: "\e152"; 57 | } 58 | 59 | .sort-numerical a.asc:after { 60 | content: "\e153"; 61 | } 62 | 63 | .sort-numerical a.desc:after { 64 | content: "\e154"; 65 | } 66 | 67 | .sort-ordinal a.asc:after { 68 | content: "\e155"; 69 | } 70 | 71 | .sort-ordinal a.desc:after { 72 | content: "\e156"; 73 | } 74 | 75 | .grid-view td { 76 | white-space: nowrap; 77 | } 78 | 79 | .grid-view .filters input, 80 | .grid-view .filters select { 81 | min-width: 50px; 82 | } 83 | 84 | .hint-block { 85 | display: block; 86 | margin-top: 5px; 87 | color: #999; 88 | } 89 | 90 | .error-summary { 91 | color: #a94442; 92 | background: #fdf7f7; 93 | border-left: 3px solid #eed3d7; 94 | padding: 10px 20px; 95 | margin: 0 0 15px 0; 96 | } 97 | 98 | /* align the logout "link" (button in form) of the navbar */ 99 | .nav li > form > button.logout { 100 | padding: 15px; 101 | border: none; 102 | } 103 | 104 | @media(max-width:767px) { 105 | .nav li > form > button.logout { 106 | display:block; 107 | text-align: left; 108 | width: 100%; 109 | padding: 10px 15px; 110 | } 111 | } 112 | 113 | .nav > li > form > button.logout:focus, 114 | .nav > li > form > button.logout:hover { 115 | text-decoration: none; 116 | } 117 | 118 | .nav > li > form > button.logout:focus { 119 | outline: none; 120 | } -------------------------------------------------------------------------------- /public/css/article.css: -------------------------------------------------------------------------------- 1 | .article { 2 | margin: 20px 0; 3 | } 4 | 5 | .article img { 6 | max-width: 120px; 7 | } 8 | 9 | .article .title { 10 | 11 | } 12 | 13 | .article .summary { 14 | margin: 10px 0; 15 | } -------------------------------------------------------------------------------- /public/img/yii2-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razonyang/yii2-app-template/46b154181add2a1d31f43b1c9cb27d6074c10f9f/public/img/yii2-app.gif -------------------------------------------------------------------------------- /public/img/yii2-vue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razonyang/yii2-app-template/46b154181add2a1d31f43b1c9cb27d6074c10f9f/public/img/yii2-vue.gif -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 15 | -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razonyang/yii2-app-template/46b154181add2a1d31f43b1c9cb27d6074c10f9f/public/js/app.js -------------------------------------------------------------------------------- /public/js/article.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var likeLock = false; 3 | var likesCount = $('#likes-count'); 4 | var btnLike = $('#btn-like'); 5 | var btnDislike = $('#btn-dislike'); 6 | 7 | $('#comment-items').load('/articles/1/comments'); 8 | 9 | function updateLikesCounter(count) { 10 | likesCount.text(parseInt(likesCount.text()) + count); 11 | } 12 | 13 | function acuquireLikeLock() { 14 | if (likeLock) { 15 | return false; 16 | } 17 | 18 | likeLock = true; 19 | return true; 20 | } 21 | 22 | function releaseLikeLock() { 23 | likeLock = false; 24 | } 25 | 26 | btnLike.click(function() { 27 | var id = $(this).attr('data-id'); 28 | $.ajax('/api/frontend/v1/articles/' + id + '/likes', { 29 | method: 'PUT', 30 | dataType: 'json', 31 | beforeSend: function() { 32 | if (!acuquireLikeLock()) { 33 | return false; 34 | } 35 | }, 36 | success: function(response) { 37 | updateLikesCounter(1); 38 | btnDislike.show(); 39 | btnLike.hide(); 40 | }, 41 | complete: function() { 42 | releaseLikeLock(); 43 | } 44 | }); 45 | }); 46 | 47 | btnDislike.click(function() { 48 | var id = $(this).attr('data-id'); 49 | $.ajax('/api/frontend/v1/articles/' + id + '/likes', { 50 | method: 'DELETE', 51 | dataType: 'json', 52 | beforeSend: function() { 53 | if (!acuquireLikeLock()) { 54 | return false; 55 | } 56 | }, 57 | success: function(response) { 58 | updateLikesCounter(-1); 59 | btnLike.show(); 60 | btnDislike.hide(); 61 | }, 62 | complete: function() { 63 | releaseLikeLock(); 64 | } 65 | }); 66 | }); 67 | }); -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /api -------------------------------------------------------------------------------- /resources/console/views/migration.php: -------------------------------------------------------------------------------- 1 | 15 | use App\Db\Migration; 16 | 17 | /** 18 | * Class 19 | */ 20 | class extends Migration 21 | { 22 | public function safeUp() 23 | { 24 | 25 | } 26 | 27 | public function safeDown() 28 | { 29 | echo " cannot be reverted.\n"; 30 | 31 | return false; 32 | } 33 | 34 | /* 35 | // Use up()/down() to run migration code without a transaction. 36 | public function up() 37 | { 38 | 39 | } 40 | 41 | public function down() 42 | { 43 | echo " cannot be reverted.\n"; 44 | 45 | return false; 46 | } 47 | */ 48 | } 49 | -------------------------------------------------------------------------------- /resources/docker/cron/crontab: -------------------------------------------------------------------------------- 1 | * * * * * /usr/local/bin/php /app/bin/yii hello # for testing 2 | * * * * * /usr/local/bin/php /app/bin/yii hello razon # for testing 3 | 4 | 0 0 * * * /usr/bin/php /app/bin/yii gc/session # flush expired sessions 5 | -------------------------------------------------------------------------------- /resources/docker/cron/image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM razonyang/php-cron:7.2 2 | RUN apt-get update 3 | 4 | # Intl 5 | RUN apt-get install -y \ 6 | zlib1g-dev \ 7 | libicu-dev \ 8 | g++ \ 9 | && docker-php-ext-configure intl \ 10 | && docker-php-ext-install intl 11 | 12 | # MySQL 13 | RUN docker-php-ext-install pdo_mysql 14 | 15 | # PostgreSQL 16 | RUN apt-get update && apt-get install -y \ 17 | libpq-dev \ 18 | && docker-php-ext-install pdo_pgsql -------------------------------------------------------------------------------- /resources/docker/cron/image/crontab: -------------------------------------------------------------------------------- 1 | * * * * * /usr/local/bin/php /app/bin/yii hello 2 | -------------------------------------------------------------------------------- /resources/docker/mysql/conf.d/mysqld.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | sql-mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION -------------------------------------------------------------------------------- /resources/docker/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | charset utf-8; 3 | client_max_body_size 128M; 4 | sendfile off; 5 | 6 | listen 80; ## listen for ipv4 7 | # listen [::]:80 default_server ipv6only=on; ## listen for ipv6 8 | 9 | server_name default_server; 10 | root /app/public/; 11 | index index.php; 12 | 13 | access_log /app/runtime/logs/access.log; 14 | error_log /app/runtime/logs/error.log; 15 | 16 | location / { 17 | try_files $uri $uri/ /index.php$is_args$args; 18 | } 19 | 20 | # uncomment to avoid processing of calls to non-existing static files by Yii 21 | location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { 22 | try_files $uri =404; 23 | } 24 | error_page 404 /404.html; 25 | 26 | location ~ \.php$ { 27 | include fastcgi_params; 28 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 29 | proxy_set_header Host $host; 30 | proxy_set_header X-Real-IP $remote_addr; 31 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 32 | proxy_set_header X-Forwarded-Host $server_name; 33 | fastcgi_pass php-fpm:9000; 34 | try_files $uri =404; 35 | } 36 | 37 | location ~ /\.(ht|svn|git) { 38 | deny all; 39 | } 40 | } -------------------------------------------------------------------------------- /resources/docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 17 | '$status $body_bytes_sent "$http_referer" ' 18 | '"$http_user_agent" "$http_x_forwarded_for"'; 19 | 20 | access_log /var/log/nginx/access.log main; 21 | 22 | sendfile on; 23 | #tcp_nopush on; 24 | 25 | keepalive_timeout 65; 26 | 27 | #gzip on; 28 | 29 | include /etc/nginx/conf.d/*.conf; 30 | } 31 | -------------------------------------------------------------------------------- /resources/docker/php/image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2-fpm 2 | RUN apt-get update 3 | 4 | # GD 5 | RUN apt-get install -y \ 6 | libfreetype6-dev \ 7 | libjpeg62-turbo-dev \ 8 | libpng-dev \ 9 | && docker-php-ext-install -j$(nproc) iconv \ 10 | && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ 11 | && docker-php-ext-install -j$(nproc) gd 12 | 13 | # Intl 14 | RUN apt-get install -y \ 15 | zlib1g-dev \ 16 | libicu-dev \ 17 | g++ \ 18 | && docker-php-ext-configure intl \ 19 | && docker-php-ext-install intl 20 | 21 | # MySQL 22 | RUN docker-php-ext-install pdo_mysql 23 | 24 | # PostgreSQL 25 | RUN apt-get update && apt-get install -y \ 26 | libpq-dev \ 27 | && docker-php-ext-install pdo_pgsql 28 | 29 | # ZIP 30 | RUN docker-php-ext-install zip 31 | -------------------------------------------------------------------------------- /resources/docker/php/php.ini: -------------------------------------------------------------------------------- 1 | expose_php = Off -------------------------------------------------------------------------------- /resources/docker/queue/image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2-cli 2 | RUN apt-get update 3 | 4 | # Intl 5 | RUN apt-get install -y \ 6 | zlib1g-dev \ 7 | libicu-dev \ 8 | g++ \ 9 | && docker-php-ext-configure intl \ 10 | && docker-php-ext-install intl 11 | 12 | # MySQL 13 | RUN docker-php-ext-install pdo_mysql 14 | 15 | # PostgreSQL 16 | RUN apt-get update && apt-get install -y \ 17 | libpq-dev \ 18 | && docker-php-ext-install pdo_pgsql -------------------------------------------------------------------------------- /resources/docker/redis/redis.conf: -------------------------------------------------------------------------------- 1 | requirepass foobar -------------------------------------------------------------------------------- /resources/environments/dev/bin/yii_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /resources/environments/dev/bin/yii_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set YII_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%YII_PATH%yii_test" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /resources/environments/dev/config/bootstrap-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=localhost;dbname=yii', 6 | 'username' => 'root', 7 | 'password' => '', 8 | 'enableSchemaCache' => false, 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | 'transport' => [ 13 | 'host' => 'smtp.example.com', 14 | 'port' => 25, 15 | 'encryption' => 'ssl', 16 | // 'username' => 'admin@example.com', 17 | 'password' => '', 18 | ], 19 | ], 20 | 'redis' => [ 21 | 'hostname' => 'localhost', 22 | 'port' => 6379, 23 | 'database' => 0, 24 | // 'password' => '', 25 | ], 26 | 'uploader' => [ 27 | 'host' => 'http://localhost:8080/resources', 28 | 'filesystem' => [ 29 | 'class' => \creocoder\flysystem\LocalFilesystem::class, 30 | 'path' => '@webroot/resources', 31 | ], 32 | ], 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /resources/environments/dev/config/console-local.php: -------------------------------------------------------------------------------- 1 | ['gii'], 4 | 'components' => [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | ], 13 | 'modules' => [ 14 | 'gii' => require __DIR__ . '/gii.php', 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /resources/environments/dev/config/params-local.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 4 | 'supportEmail' => 'support@example.com', 5 | 6 | 'backend.url' => '', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/environments/dev/config/test-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=localhost;dbname=yiitest', 6 | ], 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/environments/dev/config/web-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | 'request' => [ 13 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 14 | 'cookieValidationKey' => '', 15 | ], 16 | ], 17 | ]; 18 | 19 | if (YII_ENV_DEV) { 20 | $allowedIPs = ['127.0.0.1', '::1']; 21 | 22 | // configuration adjustments for 'dev' environment 23 | $config['bootstrap'][] = 'debug'; 24 | $config['modules']['debug'] = array_merge(require __DIR__ . '/debug.php', [ 25 | 'allowedIPs' => $allowedIPs 26 | ]); 27 | 28 | $config['bootstrap'][] = 'gii'; 29 | $config['modules']['gii'] = array_merge(require __DIR__ . '/gii.php', [ 30 | 'allowedIPs' => $allowedIPs 31 | ]); 32 | } 33 | 34 | return $config; 35 | -------------------------------------------------------------------------------- /resources/environments/docker/.env: -------------------------------------------------------------------------------- 1 | # MySQL 2 | MYSQL_ROOT_PASSWORD=foobar 3 | MYSQL_DATABASE=yii 4 | 5 | # Nginx 6 | NGINX_PORT=8080 7 | 8 | # Adminer 9 | ADMINER_PORT=8081 10 | -------------------------------------------------------------------------------- /resources/environments/docker/bin/yii_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /resources/environments/docker/bin/yii_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set YII_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%YII_PATH%yii_test" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /resources/environments/docker/config/bootstrap-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=db;dbname=yii', 6 | 'username' => 'root', 7 | 'password' => 'foobar', 8 | 'enableSchemaCache' => false, 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | 'transport' => [ 13 | 'host' => 'smtp.example.com', 14 | 'port' => 25, 15 | 'encryption' => 'ssl', 16 | // 'username' => 'admin@example.com', 17 | 'password' => '', 18 | ], 19 | ], 20 | 'redis' => [ 21 | 'hostname' => 'redis', 22 | 'port' => 6379, 23 | 'database' => 0, 24 | 'password' => 'foobar', 25 | ], 26 | 'uploader' => [ 27 | 'host' => 'http://localhost:8080/resources', 28 | 'filesystem' => [ 29 | 'class' => \creocoder\flysystem\LocalFilesystem::class, 30 | 'path' => '@webroot/resources', 31 | ], 32 | ], 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /resources/environments/docker/config/console-local.php: -------------------------------------------------------------------------------- 1 | ['gii'], 4 | 'components' => [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | ], 13 | 'modules' => [ 14 | 'gii' => require __DIR__ . '/gii.php', 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /resources/environments/docker/config/params-local.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 4 | 'supportEmail' => 'support@example.com', 5 | 6 | 'backend.url' => '', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/environments/docker/config/test-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=db;dbname=yiitest', 6 | ], 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/environments/docker/config/web-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | 'request' => [ 13 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 14 | 'cookieValidationKey' => '', 15 | ], 16 | ], 17 | ]; 18 | 19 | if (YII_ENV_DEV) { 20 | $allowedIPs = ['127.0.0.1', '::1']; 21 | 22 | // configuration adjustments for 'dev' environment 23 | $config['bootstrap'][] = 'debug'; 24 | $config['modules']['debug'] = array_merge(require __DIR__ . '/debug.php', [ 25 | 'allowedIPs' => $allowedIPs 26 | ]); 27 | 28 | $config['bootstrap'][] = 'gii'; 29 | $config['modules']['gii'] = array_merge(require __DIR__ . '/gii.php', [ 30 | 'allowedIPs' => $allowedIPs 31 | ]); 32 | } 33 | 34 | return $config; 35 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/bin/yii_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/bin/yii_test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set YII_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%YII_PATH%yii_test" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/config/bootstrap-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=' . getenv('DB_HOST') . ';dbname=' . getenv('DB_NAME'), 6 | 'username' => getenv('DB_USER'), 7 | 'password' => getenv('DB_PASSWORD'), 8 | 'enableSchemaCache' => true, 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | 'transport' => [ 13 | 'host' => getenv('MAILER_HOST'), 14 | 'port' => getenv('MAILER_PORT'), 15 | 'encryption' => 'ssl', 16 | 'username' => getenv('MAILER_USER'), 17 | 'password' => getenv('MAILER_PASSWORD'), 18 | ], 19 | ], 20 | 'redis' => [ 21 | 'hostname' => getenv("REDIS_HOST"), 22 | 'port' => 6379, 23 | 'database' => getenv("REDIS_DB"), 24 | ], 25 | 'uploader' => [ 26 | 'host' => getenv("UPLOADER_HOST"), 27 | 'filesystem' => [ 28 | 'class' => \creocoder\flysystem\LocalFilesystem::class, 29 | 'path' => '@webroot/resources', 30 | ], 31 | ], 32 | ], 33 | ]; 34 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/config/console-local.php: -------------------------------------------------------------------------------- 1 | ['gii'], 4 | 'components' => [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | ], 13 | 'modules' => [ 14 | 'gii' => require __DIR__ . '/gii.php', 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/config/params-local.php: -------------------------------------------------------------------------------- 1 | getenv('MAILER_USER'), 4 | 'supportEmail' => getenv('MAILER_USER'), 5 | 6 | 'backend.url' => getenv('BACKEND_URL'), 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/config/test-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=db;dbname=yiitest', 6 | ], 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/environments/dockerenv/config/web-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'log' => [ 6 | 'targets' => [ 7 | 'file' => [ 8 | 'levels' => ['info', 'trace'], 9 | ], 10 | ], 11 | ], 12 | 'request' => [ 13 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 14 | 'cookieValidationKey' => '', 15 | ], 16 | ], 17 | ]; 18 | 19 | return $config; 20 | -------------------------------------------------------------------------------- /resources/environments/prod/config/bootstrap-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=localhost;dbname=yii', 6 | 'username' => 'root', 7 | 'password' => '', 8 | 'enableSchemaCache' => false, 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | 'transport' => [ 13 | 'host' => 'smtp.example.com', 14 | 'port' => 25, 15 | 'encryption' => 'ssl', 16 | // 'username' => 'admin@example.com', 17 | 'password' => '', 18 | ], 19 | ], 20 | 'redis' => [ 21 | 'hostname' => 'localhost', 22 | 'port' => 6379, 23 | 'database' => 0, 24 | 'password' => '', 25 | ], 26 | 'uploader' => [ 27 | 'host' => 'http://localhost:8080/resources', 28 | 'filesystem' => [ 29 | 'class' => \creocoder\flysystem\LocalFilesystem::class, 30 | 'path' => '@webroot/resources', 31 | ], 32 | ], 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /resources/environments/prod/config/console-local.php: -------------------------------------------------------------------------------- 1 | ['gii'], 4 | 'modules' => [ 5 | 'gii' => 'yii\gii\Module', 6 | ], 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/environments/prod/config/params-local.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 4 | 'supportEmail' => 'support@example.com', 5 | 6 | 'backend.url' => '', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/environments/prod/config/test-local.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'mysql:host=db;dbname=yii_test', 6 | ], 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/environments/prod/config/web-local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'request' => [ 6 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 7 | 'cookieValidationKey' => '', 8 | ], 9 | ], 10 | ]; 11 | 12 | return $config; 13 | -------------------------------------------------------------------------------- /resources/gii/generators/module/views/controller.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | namespace getControllerNamespace() ?>; 13 | 14 | use yii\web\Controller; 15 | 16 | /** 17 | * Default controller for the `moduleID ?>` module 18 | */ 19 | class DefaultController extends Controller 20 | { 21 | /** 22 | * Renders the index view for the module 23 | * @return string 24 | */ 25 | public function actionIndex() 26 | { 27 | return $this->render('index'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/gii/generators/module/views/form.php: -------------------------------------------------------------------------------- 1 | 7 |
8 | field($generator, 'moduleClass'); 10 | echo $form->field($generator, 'moduleID'); 11 | echo $form->field($generator, 'viewPath'); 12 | ?> 13 |
14 | -------------------------------------------------------------------------------- /resources/gii/generators/module/views/module.php: -------------------------------------------------------------------------------- 1 | moduleClass; 10 | $pos = strrpos($className, '\\'); 11 | $ns = ltrim(substr($className, 0, $pos), '\\'); 12 | $className = substr($className, $pos + 1); 13 | 14 | echo " 16 | 17 | namespace ; 18 | 19 | /** 20 | * moduleID ?> module definition class 21 | */ 22 | class extends \yii\base\Module 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public $controllerNamespace = 'getControllerNamespace() ?>'; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function init() 33 | { 34 | parent::init(); 35 | 36 | // custom initialization code goes here 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /resources/gii/generators/module/views/view.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

$this->context->action->uniqueId ?>

7 |

8 | This is the view content for action "$this->context->action->id ?>". 9 | The action belongs to the controller "get_class($this->context) ?>" 10 | in the "$this->context->module->id ?>" module. 11 |

12 |

13 | You may customize this page by editing the following file:
14 | __FILE__ ?> 15 |

16 |
17 | -------------------------------------------------------------------------------- /resources/mail/emailVerify-html.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $user->verification_token]); 8 | ?> 9 |
10 |

Hello username) ?>,

11 | 12 |

Follow the link below to verify your email:

13 | 14 |

15 |
16 | -------------------------------------------------------------------------------- /resources/mail/emailVerify-text.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $user->verification_token]); 7 | ?> 8 | Hello username ?>, 9 | 10 | Follow the link below to verify your email: 11 | 12 | 13 | -------------------------------------------------------------------------------- /resources/mail/hello.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | Hello -------------------------------------------------------------------------------- /resources/mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 8 | beginPage() ?> 9 | 10 | 11 | 12 | 13 | <?= Html::encode($this->title) ?> 14 | head() ?> 15 | 16 | 17 | beginBody() ?> 18 | 19 | endBody() ?> 20 | 21 | 22 | endPage() ?> 23 | -------------------------------------------------------------------------------- /resources/mail/layouts/text.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | beginPage() ?> 11 | beginBody() ?> 12 | 13 | endBody() ?> 14 | endPage() ?> 15 | -------------------------------------------------------------------------------- /resources/mail/passwordResetToken-html.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); 8 | ?> 9 |
10 |

Hello username) ?>,

11 | 12 |

Follow the link below to reset your password:

13 | 14 |

15 |
16 | -------------------------------------------------------------------------------- /resources/mail/passwordResetToken-text.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); 7 | ?> 8 | Hello username ?>, 9 | 10 | Follow the link below to reset your password: 11 | 12 | 13 | -------------------------------------------------------------------------------- /resources/messages/zh-CN/app.php: -------------------------------------------------------------------------------- 1 | '关于我们', 5 | 'Article' => '文章', 6 | 7 | 'Console' => '控制台', 8 | 'Contact Us' => '联系我们', 9 | 'Content' => '内容', 10 | 11 | 'Description' => '描述', 12 | 13 | 'Email' => '电子邮箱', 14 | 15 | 'First Name' => '名称', 16 | 17 | 'Home' => '主页', 18 | 19 | 'Last Name' => '姓氏', 20 | 'Login' => '登录', 21 | 'Logout' => '注销', 22 | 23 | 'Name' => '名称', 24 | 'New Password' => '新密码', 25 | 26 | 'Original password is incorrect' => '原密码不正确', 27 | 'Original Password' => '原密码', 28 | 29 | 'Password' => '密码', 30 | 'Permission' => '权限', 31 | 32 | 'Real Name' => '真实姓名', 33 | 'Reset Password' => '重置密码', 34 | 'Role' => '角色', 35 | 36 | 'Setting' => '设置', 37 | 'Sign Up' => '注册', 38 | 'Subject' => '主题', 39 | 40 | 'User' => '用户', 41 | 'Username' => '用户名', 42 | 43 | 'Verification Code' => '验证码', 44 | ]; 45 | -------------------------------------------------------------------------------- /resources/messages/zh-TW/app.php: -------------------------------------------------------------------------------- 1 | '關於我們', 5 | 'Article' => '文章', 6 | 7 | 'Console' => '控制臺', 8 | 'Contact Us' => '聯系我們', 9 | 'Content' => '內容', 10 | 11 | 'Description' => '描述', 12 | 13 | 'Email' => '電子郵箱', 14 | 15 | 'First Name' => '名稱', 16 | 17 | 'Home' => '主頁', 18 | 19 | 'Last Name' => '姓氏', 20 | 'Login' => '登錄', 21 | 'Logout' => '注銷', 22 | 23 | 'Name' => '名稱', 24 | 'New Password' => '新密碼', 25 | 26 | 'Original password is incorrect' => '原密碼不正確', 27 | 'Original Password' => '原密碼', 28 | 29 | 'Password' => '密碼', 30 | 'Permission' => '權限', 31 | 32 | 'Real Name' => '真實姓名', 33 | 'Reset Password' => '重置密碼', 34 | 'Role' => '角色', 35 | 36 | 'Setting' => '設置', 37 | 'Sign Up' => '注冊', 38 | 'Subject' => '主題', 39 | 40 | 'User' => '用戶', 41 | 'Username' => '用戶名', 42 | 43 | 'Verification Code' => '驗證碼', 44 | ]; 45 | -------------------------------------------------------------------------------- /resources/migrations/m190617_031446_table_user.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'id' => $this->primaryKey(), 15 | 'username' => $this->string()->notNull()->unique(), 16 | 'first_name' => $this->string()->notNull()->defaultValue('')->comment('First Name'), 17 | 'last_name' => $this->string()->notNull()->defaultValue('')->comment('Last Name'), 18 | 'avatar' => $this->string()->notNull()->defaultValue('')->comment('Avatar'), 19 | 'auth_key' => $this->string(32)->notNull(), 20 | 'password_hash' => $this->string()->notNull(), 21 | 'password_reset_token' => $this->string()->unique(), 22 | 'email' => $this->string()->notNull()->unique(), 23 | 'verification_token' => $this->string()->defaultValue(null), 24 | 'language' => $this->string(5)->notNull()->defaultValue('en'), 25 | 26 | 'status' => $this->status(), 27 | 'is_deleted' => $this->softDelete(), 28 | 'create_time' => $this->createTimestamp(), 29 | 'update_time' => $this->updateTimestamp(), 30 | ], $this->tableOptions()); 31 | 32 | $this->createIndex('user_idx_status', $this->tableName, ['status']); 33 | $this->createIndex('user_idx_is_deleted', $this->tableName, ['is_deleted']); 34 | } 35 | 36 | public function down() 37 | { 38 | $this->dropTable($this->tableName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/migrations/m190722_062914_table_session.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'id' => $this->uuid(), 15 | 'user_id' => $this->integer()->notNull()->comment('User ID'), 16 | 'token' => $this->char(32)->notNull()->unique()->comment('Access Token'), 17 | 'refresh_token' => $this->char(64)->notNull()->unique()->comment('Refresh Token'), 18 | 'ip_address' => $this->ipAddress(), 19 | 'user_agent' => $this->string()->notNull()->comment('User Agent'), 20 | 21 | 'expire_time' => $this->timestamp('Expire Time'), 22 | 'refresh_token_expire_time' => $this->timestamp('Refresh Token Expire Time'), 23 | 'create_time' => $this->createTimestamp(), 24 | 'update_time' => $this->updateTimestamp(), 25 | ], $this->tableOptions()); 26 | 27 | $this->addPrimaryKey('session_pk', $this->tableName, ['id']); 28 | $this->createIndex('session_idx_user_id_expire_time', $this->tableName, ['user_id', 'expire_time']); 29 | $this->createIndex('session_idx_create_time', $this->tableName, ['create_time']); 30 | } 31 | 32 | public function safeDown() 33 | { 34 | $this->dropTable($this->tableName); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/migrations/m190724_031648_data_user.php: -------------------------------------------------------------------------------- 1 | getSecurity()->generatePasswordHash('123456'), '', 'admin@example.com', 23 | StatusInterface::STATUS_ACTIVE, $now, $now, 24 | ], 25 | ]; 26 | $this->batchInsert(User::tableName(), $columns, $rows); 27 | 28 | $auth = Yii::$app->getAuthManager(); 29 | $auth->assign($auth->getRole('Administrator'), 1); 30 | } 31 | 32 | public function safeDown() 33 | { 34 | $this->delete(User::tableName(), ['username' => 'admin']); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/migrations/m190731_024219_table_article.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'id' => $this->primaryKey(), 15 | 'category_id' => $this->integer()->notNull()->comment('Category ID'), 16 | 'creator' => $this->integer()->notNull()->comment('Creator ID'), 17 | 'title' => $this->string()->notNull()->comment('Title'), 18 | 'author' => $this->string()->notNull()->defaultValue('')->comment('Author'), 19 | 'summary' => $this->string()->notNull()->comment('Summary'), 20 | 'cover' => $this->string()->notNull()->comment('Cover'), 21 | 'status' => $this->status(), 22 | 'is_deleted' => $this->softDelete(), 23 | 'views' => $this->integer()->unsigned()->notNull()->defaultValue(0), 24 | 'version' => $this->optimisticLock(), 25 | 'release_time' => $this->timestamp('Release Time'), 26 | 'create_time' => $this->createTimestamp(), 27 | 'update_time' => $this->updateTimestamp(), 28 | ], $this->tableOptions()); 29 | 30 | $this->createIndex('article_idx_creator', $this->tableName, ['creator']); 31 | $this->createIndex('article_idx_status', $this->tableName, ['status']); 32 | $this->createIndex('article_idx_is_deleted', $this->tableName, ['is_deleted']); 33 | $this->createIndex('article_idx_release_time', $this->tableName, ['release_time']); 34 | $this->createIndex('article_idx_category_id', $this->tableName, 'category_id'); 35 | } 36 | 37 | public function safeDown() 38 | { 39 | $this->dropTable($this->tableName); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /resources/migrations/m190822_022858_table_article_meta.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'article_id' => $this->integer()->notNull()->unique()->comment('Article ID'), 15 | 'content' => $this->text()->notNull()->comment('Content'), 16 | ], $this->tableOptions()); 17 | } 18 | 19 | public function safeDown() 20 | { 21 | $this->dropTable($this->tableName); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/migrations/m190822_034841_table_article_category.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'id' => $this->primaryKey(), 15 | 'creator' => $this->integer()->notNull()->comment('Creator ID'), 16 | 'name' => $this->string(32)->notNull()->comment('Name'), 17 | 'create_time' => $this->createTimestamp(), 18 | 'update_time' => $this->updateTimestamp(), 19 | ], $this->tableOptions()); 20 | 21 | $this->createIndex('article_category_idx_creator', $this->tableName, ['creator']); 22 | } 23 | 24 | public function safeDown() 25 | { 26 | $this->dropTable($this->tableName); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/migrations/m190822_070400_data_article_category.php: -------------------------------------------------------------------------------- 1 | batchInsert($this->tableName, $columns, $rows); 20 | } 21 | 22 | public function safeDown() 23 | { 24 | $this->truncateTable($this->tableName); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/migrations/m190822_070404_data_article.php: -------------------------------------------------------------------------------- 1 | insert($this->tableName, [ 17 | 'id' => 1, 18 | 'title' => 'Hello World', 19 | 'creator' => 1, 20 | 'author' => 'Administrator', 21 | 'category_id' => 1, 22 | 'summary' => 'Congratulation! You have set up Yii2 and Vue application successfully.', 23 | 'cover' => '', 24 | 'release_time' => $now, 25 | 'create_time' => $now, 26 | 'update_time' => $now, 27 | ]); 28 | 29 | $this->insert($this->metaTableName, [ 30 | 'article_id' => 1, 31 | 'content' => '

Congratulation! You have set up Yii2 and Vue application successfully.

', 32 | ]); 33 | } 34 | 35 | public function safeDown() 36 | { 37 | $this->truncateTable($this->tableName); 38 | $this->truncateTable($this->metaTableName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/migrations/m190829_053119_table_article_like.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'article_id' => $this->integer()->notNull()->comment('Article ID'), 15 | 'user_id' => $this->integer()->notNull()->comment('User ID'), 16 | 'create_time' => $this->createTimestamp(), 17 | ], $this->tableOptions()); 18 | 19 | $this->addPrimaryKey('article_like_pk', $this->tableName, ['article_id', 'user_id']); 20 | } 21 | 22 | public function safeDown() 23 | { 24 | $this->dropTable($this->tableName); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/migrations/m190829_093604_article_comment.php: -------------------------------------------------------------------------------- 1 | createTable($this->tableName, [ 14 | 'id' => $this->bigPrimaryKey(), 15 | 'reply_to' => $this->bigInteger()->notNull()->defaultValue(0)->comment('Reply To'), 16 | 'article_id' => $this->integer()->notNull()->comment('Article ID'), 17 | 'user_id' => $this->integer()->notNull()->comment('User ID'), 18 | 'content' => $this->text()->notNull()->comment('Content'), 19 | 'is_deleted' => $this->softDelete(), 20 | 'create_time' => $this->createTimestamp(), 21 | 'update_time' => $this->updateTimestamp(), 22 | ], $this->tableOptions()); 23 | 24 | $this->createIndex('article_comment_idx_reply_to', $this->tableName, 'reply_to'); 25 | $this->createIndex('article_comment_idx_article_id', $this->tableName, 'article_id'); 26 | $this->createIndex('article_comment_idx_user_id', $this->tableName, 'user_id'); 27 | $this->createIndex('article_comment_idx_create_time', $this->tableName, 'create_time'); 28 | } 29 | 30 | public function safeDown() 31 | { 32 | $this->dropTable($this->tableName); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /resources/queue/gii/views/form.php: -------------------------------------------------------------------------------- 1 | 8 | field($generator, 'jobClass')->textInput(['autofocus' => true]) ?> 9 | field($generator, 'properties') ?> 10 | field($generator, 'retryable')->checkbox() ?> 11 | field($generator, 'ns') ?> 12 | field($generator, 'baseClass') ?> 13 | -------------------------------------------------------------------------------- /resources/queue/gii/views/job.php: -------------------------------------------------------------------------------- 1 | 19 | namespace ; 20 | 21 | /** 22 | * Class . 23 | */ 24 | class extends 25 | 26 | { 27 | retryable): ?> 28 | use retryableTrait ?>; 29 | 30 | 31 | 32 | public $; 33 | 34 | 35 | protected function run() 36 | { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /resources/views/article/comments.php: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | 11 | 12 | 13 |
14 |
15 |

user->name) ?>

16 | content) ?> 17 |
18 |
19 | -------------------------------------------------------------------------------- /resources/views/article/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Article'); 13 | $this->params['breadcrumbs'][] = $this->title; 14 | 15 | $this->registerCssFile('/css/article.css', ['depends' => [AppAsset::class]]); 16 | ?> 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |

26 | title), Url::to(['/article/view', 'id' => $article->id])) ?> 27 | category->name ?? '') ?> 28 |

29 | 30 |

summary) ?>

31 | 32 | 33 | Created by author) ?> and released at formatter->asDate($article->release_time) ?> 34 | views ?> 35 | id] ?? 0 ?> 36 | 37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 | $pagination, 46 | ]);?> -------------------------------------------------------------------------------- /resources/views/article/view.php: -------------------------------------------------------------------------------- 1 | title = Html::encode($model->title); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Article'), 'url' => Url::to('/articles')]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | $this->registerJsFile(Yii::$app->getUrlManager()->createUrl('/js/article.js'), ['depends' => AppAsset::class]); 15 | ?> 16 | 17 |

18 | title) ?> 19 | category->name ?? '') ?> 20 |

21 | 22 |
23 | 24 |

25 | Created by author) ?> and released at formatter->asDate($model->release_time) ?> 26 | views ?> 27 | getUser()->getIsGuest()): ?> 28 | 29 | 30 | $model->getHasLiked() ? '' : 'display: none;', 32 | 'id' => 'btn-dislike', 33 | 'data-id' => $model->id, 34 | ]) ?> 35 | $model->getHasLiked() ? 'display: none;' : '', 37 | 'id' => 'btn-like', 38 | 'data-id' => $model->id, 39 | ]) ?> 40 | 41 | getLikesCount() ?> 42 |

43 | 44 | 45 | 46 |
47 | 48 | 49 | 'form-control']) ?> 50 | 'btn btn-primary']) ?> 51 | 52 | 53 |
54 |
-------------------------------------------------------------------------------- /resources/views/site/about.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'About Us'); 8 | $this->params['breadcrumbs'][] = $this->title; 9 | ?> 10 |
11 |

title) ?>

12 |
13 | -------------------------------------------------------------------------------- /resources/views/site/contact.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Contact Us'); 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 |

title) ?>

15 |
16 | 17 |

18 | If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. 19 |

20 | 21 |
22 |
23 | 'contact-form']); ?> 24 | 25 | field($model, 'name')->textInput(['autofocus' => true]) ?> 26 | 27 | field($model, 'email') ?> 28 | 29 | field($model, 'subject') ?> 30 | 31 | field($model, 'body')->textarea(['rows' => 6]) ?> 32 | 33 | field($model, 'verifyCode')->widget(Captcha::className(), [ 34 | 'template' => '
{image}
{input}
', 35 | ]) ?> 36 | 37 |
38 | 'btn btn-primary', 'name' => 'contact-button']) ?> 39 |
40 | 41 | 42 |
43 |
-------------------------------------------------------------------------------- /resources/views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 |
17 | 18 |
19 | 20 |

21 | The above error occurred while the Web server was processing your request. 22 |

23 |

24 | Please contact us if you think this is a server error. Thank you. 25 |

26 | 27 |
28 | -------------------------------------------------------------------------------- /resources/views/site/index.php: -------------------------------------------------------------------------------- 1 | getUrlManager(); 9 | 10 | $this->title = Yii::t('app', 'Home'); 11 | ?> 12 |
13 | 14 |
15 |

Congratulation!

16 |
17 |

You have set up Yii2 and Vue application successfully.

18 |

19 | Yii2 App Template 20 | Yii2 Vue Admin 21 |

22 | 23 | [ 25 | 'Previous', 26 | 'Next' 27 | ], 28 | 'items' => [ 29 | [ 30 | 'content' => Html::img($urlManager->createUrl(['/img/yii2-app.gif'])), 31 | 'caption' => '

Install Yii2 Application via Docker

', 32 | 'options' => [], 33 | ], 34 | [ 35 | 'content' => Html::img($urlManager->createUrl(['/img/yii2-vue.gif'])), 36 | 'caption' => '

Install Yii2 Vue Admin

', 37 | 'options' => [], 38 | ], 39 | ], 40 | 'clientOptions' => [ 41 | 'interval' => false, 42 | ], 43 | ]) ?> 44 | 45 |
46 | -------------------------------------------------------------------------------- /resources/views/site/login.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Login'); 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 44 | -------------------------------------------------------------------------------- /resources/views/site/requestPasswordResetToken.php: -------------------------------------------------------------------------------- 1 | title = 'Request password reset'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 |

title) ?>

15 | 16 |

Please fill out your email. A link to reset password will be sent there.

17 | 18 |
19 |
20 | 'request-password-reset-form']); ?> 21 | 22 | field($model, 'email')->textInput(['autofocus' => true]) ?> 23 | 24 |
25 | 'btn btn-primary']) ?> 26 |
27 | 28 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/site/resendVerificationEmail.php: -------------------------------------------------------------------------------- 1 | title = 'Resend verification email'; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 |

title) ?>

14 | 15 |

Please fill out your email. A verification email will be sent there.

16 | 17 |
18 |
19 | 'resend-verification-email-form']); ?> 20 | 21 | field($model, 'email')->textInput(['autofocus' => true]) ?> 22 | 23 |
24 | 'btn btn-primary']) ?> 25 |
26 | 27 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /resources/views/site/resetPassword.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Reset Password'); 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 |

title) ?>

15 | 16 |

Please choose your new password:

17 | 18 |
19 |
20 | 'reset-password-form']); ?> 21 | 22 | field($model, 'password')->passwordInput(['autofocus' => true]) ?> 23 | 24 |
25 | 'btn btn-primary']) ?> 26 |
27 | 28 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/site/signup.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Sign Up'); 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 40 | -------------------------------------------------------------------------------- /tests/Acceptance/HomeCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/')); 10 | $I->see('Congratulation!'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'class' => UserFixture::className(), 23 | 'dataFile' => codecept_data_dir() . 'user.php' 24 | ] 25 | ]; 26 | } 27 | 28 | public function testLoginNoUser() 29 | { 30 | $model = new LoginForm([ 31 | 'username' => 'not_existing_username', 32 | 'password' => 'not_existing_password', 33 | ]); 34 | 35 | $model->handle(); 36 | $this->assertTrue($model->hasErrors()); 37 | $this->assertArrayHasKey('password', $model->getErrors()); 38 | $this->assertTrue(Yii::$app->user->isGuest); 39 | } 40 | 41 | public function testLoginWrongPassword() 42 | { 43 | $model = new LoginForm([ 44 | 'username' => 'bayer.hudson', 45 | 'password' => 'wrong_password', 46 | ]); 47 | 48 | $model->handle(); 49 | $this->assertTrue($model->hasErrors()); 50 | $this->assertArrayHasKey('password', $model->getErrors()); 51 | $this->assertTrue(Yii::$app->user->isGuest); 52 | } 53 | 54 | public function testLoginCorrect() 55 | { 56 | $model = new LoginForm([ 57 | 'username' => 'bayer.hudson', 58 | 'password' => 'password_0', 59 | ]); 60 | 61 | $model->handle(); 62 | $this->assertFalse($model->hasErrors()); 63 | $this->assertFalse(Yii::$app->user->isGuest); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | $name, 16 | ]); 17 | 18 | $method = new \ReflectionMethod(HelloJob::class, 'run'); 19 | $method->setAccessible(true); 20 | ob_start(); 21 | $method->invoke($job); 22 | $output = ob_get_contents(); 23 | ob_end_flush(); 24 | $this->assertContains($name, $output); 25 | } 26 | 27 | public function dataRun(): array 28 | { 29 | return [ 30 | ['foo'], 31 | ['bar'], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Unit/Factory/ArticleMetaFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertSame($articleId, $model->article_id); 18 | $this->assertSame($content, $model->content); 19 | } 20 | 21 | public function dataCreate(): array 22 | { 23 | return [ 24 | [1, 'foo'], 25 | [2, 'bar'], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Unit/Factory/SessionFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertSame($userId, $session->user_id); 19 | $this->assertTrue($duration + time() >= $session->expire_time); 20 | $this->assertTrue($refeshDuration + time() >= $session->refresh_token_expire_time); 21 | $this->assertEquals($request->getUserIP(), $session->ip_address); 22 | $this->assertEquals($request->getUserAgent(), $session->user_agent); 23 | } 24 | 25 | public function dataCreate(): array 26 | { 27 | return [ 28 | [1, 2, 3, new \yii\web\Request()], 29 | [4, 5, 6, new \yii\web\Request()], 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Unit/Helper/DbHelperTest.php: -------------------------------------------------------------------------------- 1 | expectException(\Throwable::class); 16 | } 17 | DbHelper::transaction($callback, $parameters); 18 | } 19 | 20 | public function dataTransaction(): array 21 | { 22 | return [ 23 | [function () { 24 | }, [], true], 25 | [function () { 26 | throw new \Exception('fail'); 27 | }, [], false], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Unit/Validator/UrlValidatorTest.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 17 | $result = $method->invoke($validator, $url); 18 | if ($valid) { 19 | $this->assertNull($result); 20 | } else { 21 | $this->assertTrue(is_array($result)); 22 | $this->assertCount(2, $result); 23 | } 24 | } 25 | 26 | public function dataProviderUrl(): array 27 | { 28 | return [ 29 | ['http://localhost', true], 30 | ['http://localhost:8080', true], 31 | ['localhost:8080', false], 32 | ['ftp://localhost:8080', false], 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'bayer.hudson', 6 | 'auth_key' => 'HP187Mvq7Mmm3CTU80dLkGmni_FUH_lR', 7 | //password_0 8 | 'password_hash' => '$2y$13$EjaPFBnZOQsHdGuHI.xvhuDp1fHpo8hKRSk6yshqa9c5EG8s3C3lO', 9 | 'password_reset_token' => 'ExzkCOaYc1L8IOBs4wdTGGbgNiG3Wz1I_1402312317', 10 | 'create_time' => '1402312317', 11 | 'update_time' => '1402312317', 12 | 'email' => 'nicole.paucek@schultz.info', 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/_support/AcceptanceTester.php: -------------------------------------------------------------------------------- 1 |