Initial commit
This commit is contained in:
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
.vscode
|
||||
# yii console commands
|
||||
/yii
|
||||
/yii_test
|
||||
/yii_test.bat
|
||||
mysql-data
|
||||
|
||||
# phpstorm project files
|
||||
.idea
|
||||
|
||||
# netbeans project files
|
||||
nbproject
|
||||
|
||||
# zend studio for eclipse project files
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# windows thumbnail cache
|
||||
Thumbs.db
|
||||
|
||||
# composer vendor dir
|
||||
/vendor
|
||||
|
||||
# composer itself is not needed
|
||||
composer.phar
|
||||
|
||||
|
||||
# Mac DS_Store Files
|
||||
.DS_Store
|
||||
|
||||
# phpunit itself is not needed
|
||||
phpunit.phar
|
||||
# local phpunit config
|
||||
/phpunit.xml
|
||||
|
||||
# vagrant runtime
|
||||
/.vagrant
|
||||
|
||||
# ignore generated files
|
||||
/frontend/web/index.php
|
||||
/frontend/web/index-test.php
|
||||
/frontend/web/robots.txt
|
||||
/backend/web/index.php
|
||||
/backend/web/index-test.php
|
||||
/backend/web/robots.txt
|
||||
/frontend/web/uploads/
|
||||
17
CHANGELOG.md
Normal file
17
CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
* Initial release
|
||||
|
||||
## [0.1.0] - 2025-02-10
|
||||
|
||||
* First release
|
||||
|
||||
[Unreleased]: https://github.com/cgsmith/calorie/compare/0.0.1...HEAD
|
||||
[0.1.0]: https://github.com/cgsmith/calorie/releases/tag/0.1.0
|
||||
110
README.md
Normal file
110
README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Food Tracker Application - Yii2 MVP
|
||||
|
||||
This document outlines the development plan for a minimal viable product (MVP) of a food tracking application built
|
||||
using the Yii2 framework.
|
||||
|
||||
Read about it in the [MVP](mvp.md)
|
||||
|
||||
## Application Overview
|
||||
|
||||
The application allows users to log meals, including uploading images for analysis, and view daily nutritional
|
||||
summaries. This MVP focuses on core functionality, prioritizing ease of development and rapid iteration. Social login is
|
||||
not included in this version.
|
||||
|
||||
## Development Setup
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you have any problems with these steps please [create an issue](../../issues/new)
|
||||
|
||||
You will need a few items locally before setting up. Everything runs in `docker` except for the first few setup items.
|
||||
On your host download or make sure you have:
|
||||
|
||||
* [PHP 8.3+](https://www.php.net)
|
||||
* [composer](https://getcomposer.org/)
|
||||
* [docker](https://docs.docker.com/desktop/)
|
||||
|
||||
After having the necessary software then you can perform the following steps to setup your test instance:
|
||||
|
||||
1. `git clone git@github.com:cgsmith/calorie.git`
|
||||
2. `cd calorie`
|
||||
3. `composer install`
|
||||
4. `docker compose up -d`
|
||||
5. `php init --env=Development`
|
||||
|
||||
You're application should be running at http://localhost:20080!
|
||||
|
||||
### Database initialization
|
||||
|
||||
1. `docker exec -it calorie-frontend-1 bash`
|
||||
2. `yii migrate`
|
||||
3. `yii fixture/load "*"` (creates chris@fivedevs.com with password of `password`)
|
||||
|
||||
🎉 You should be able to login!
|
||||
|
||||
### Setting up Xdebug Locally
|
||||
|
||||
Xdebug is installed and configured on the docker container.
|
||||
In [PhpStorm](https://www.jetbrains.com/help/phpstorm/configuring-xdebug.html#integrationWithProduct) you will need
|
||||
to still configure it.
|
||||
|
||||
#### PhpStorm Setup
|
||||
|
||||
1. `Ctrl + Alt + S` to open settings
|
||||
2. Goto `PHP > Servers`
|
||||
3. Add a new server called 'Calorie'
|
||||
1. Host: `localhost`
|
||||
2. Port: `20080`
|
||||
3. Check `Use path mappings`
|
||||
4. Map the repo to the app folder: `~/calorie -> /app`
|
||||
4. Under `PHP > Debug` in the settings screen add the following ports to listen on: `9005`
|
||||
|
||||
You can add the port by adding a comma to separate.
|
||||
|
||||
#### VSCode setup
|
||||
|
||||
1. Open extensions `Ctrl + Shift + X`
|
||||
2. Download PHP Debug extension (publisher is xdebug.org)
|
||||
3. Goto `Run > Add Configuration` menu
|
||||
4. Select PHP
|
||||
5. Change the port setting to `9005`
|
||||
|
||||
Your VSCode IDE is now ready to start receiving Xdebug signals! For more documentation on setup please
|
||||
see [Xdebug extension documentation](https://github.com/xdebug/vscode-php-debug)
|
||||
|
||||
## Testing
|
||||
|
||||
> [!NOTE]
|
||||
> Tests should run within the docker container
|
||||
> Run with `docker exec -e XDEBUG_MODE=off calorie-frontend-1 ./vendor/bin/codecept run` from your
|
||||
> host.
|
||||
|
||||
For running tests the project uses [Codeception](https://codeception.com). To run these tests just run `composer test`.
|
||||
You can also run this by running `./vendor/bin/codecept run` which will take the entire `codeception.yml` and run the
|
||||
tests.
|
||||
|
||||
These will also run automatically on deployment.
|
||||
|
||||
## Deployment
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Follow Semantic Versioning and update the CHANGELOG when making a release! Sentry manages releases with the SHA
|
||||
> from git - while we manage the release with version numbers in a sane way.
|
||||
|
||||
[Deployer](https://deployer.org) is used for the atomic deployments. An atomic deployment simply changes the symlink for
|
||||
the webserver and then restarts the webserver after running any database migrations. This process, like all processes,
|
||||
can always be improved upon. An atomic deployment allows a server administrator to symlink to a prior version of working
|
||||
code as long as they navigate to the correct git SHA and change the symlink.
|
||||
|
||||
Deployer can be run from the command line with a command like below:
|
||||
|
||||
** Deploy to testing **
|
||||
|
||||
```shell
|
||||
./vendor/bin/dep deploy test.calorie
|
||||
```
|
||||
|
||||
** Deploy to production **
|
||||
|
||||
```shell
|
||||
./vendor/bin/dep deploy calorie --tag=1.0.0 # change your tag here
|
||||
```
|
||||
0
api/Dockerfile
Normal file
0
api/Dockerfile
Normal file
1
api/assets/AppAsset.php
Normal file
1
api/assets/AppAsset.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php
|
||||
15
api/codeception.yml
Normal file
15
api/codeception.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace: backend\tests
|
||||
actor_suffix: Tester
|
||||
paths:
|
||||
tests: tests
|
||||
output: tests/_output
|
||||
data: tests/_data
|
||||
support: tests/_support
|
||||
bootstrap: _bootstrap.php
|
||||
settings:
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
modules:
|
||||
config:
|
||||
Yii2:
|
||||
configFile: 'config/codeception-local.php'
|
||||
0
api/config/.gitignore
vendored
Normal file
0
api/config/.gitignore
vendored
Normal file
0
api/config/bootstrap.php
Normal file
0
api/config/bootstrap.php
Normal file
0
api/config/codeception-local.php
Normal file
0
api/config/codeception-local.php
Normal file
25
api/config/main-local.php
Normal file
25
api/config/main-local.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
$config = [
|
||||
'components' => [
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => 'fKCQhZtDFPI3Xt13xTqMdvn16F_Yd1Sl',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (!YII_ENV_TEST) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
$config['bootstrap'][] = 'debug';
|
||||
$config['modules']['debug'] = [
|
||||
'class' => \yii\debug\Module::class,
|
||||
];
|
||||
|
||||
$config['bootstrap'][] = 'gii';
|
||||
$config['modules']['gii'] = [
|
||||
'class' => \yii\gii\Module::class,
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
50
api/config/main.php
Normal file
50
api/config/main.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
$params = array_merge(
|
||||
require __DIR__ . '/../../common/config/params.php',
|
||||
require __DIR__ . '/../../common/config/params-local.php',
|
||||
require __DIR__ . '/params.php',
|
||||
require __DIR__ . '/params-local.php'
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => 'app-backend',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'controllerNamespace' => 'backend\controllers',
|
||||
'bootstrap' => ['log'],
|
||||
'modules' => [],
|
||||
'components' => [
|
||||
'request' => [
|
||||
'csrfParam' => '_csrf-backend',
|
||||
],
|
||||
'user' => [
|
||||
'identityClass' => 'common\models\User',
|
||||
'enableAutoLogin' => true,
|
||||
'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true],
|
||||
],
|
||||
'session' => [
|
||||
// this is the name of the session cookie used for login on the backend
|
||||
'name' => 'advanced-backend',
|
||||
],
|
||||
'log' => [
|
||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||
'targets' => [
|
||||
[
|
||||
'class' => \yii\log\FileTarget::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'errorHandler' => [
|
||||
'errorAction' => 'site/error',
|
||||
],
|
||||
/*
|
||||
'urlManager' => [
|
||||
'enablePrettyUrl' => true,
|
||||
'showScriptName' => false,
|
||||
'rules' => [
|
||||
],
|
||||
],
|
||||
*/
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
0
api/config/params-local.php
Normal file
0
api/config/params-local.php
Normal file
0
api/config/params.php
Normal file
0
api/config/params.php
Normal file
0
api/config/test-local.php
Normal file
0
api/config/test-local.php
Normal file
0
api/config/test.php
Normal file
0
api/config/test.php
Normal file
106
api/controllers/SiteController.php
Normal file
106
api/controllers/SiteController.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace backend\controllers;
|
||||
|
||||
use common\models\LoginForm;
|
||||
use Yii;
|
||||
use yii\filters\VerbFilter;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
/**
|
||||
* Site controller
|
||||
*/
|
||||
class SiteController extends Controller
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['login', 'error'],
|
||||
'allow' => true,
|
||||
],
|
||||
[
|
||||
'actions' => ['logout', 'index'],
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'verbs' => [
|
||||
'class' => VerbFilter::class,
|
||||
'actions' => [
|
||||
'logout' => ['post'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return [
|
||||
'error' => [
|
||||
'class' => \yii\web\ErrorAction::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays homepage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionIndex()
|
||||
{
|
||||
|
||||
return $this->render('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Login action.
|
||||
*
|
||||
* @return string|Response
|
||||
*/
|
||||
public function actionLogin()
|
||||
{
|
||||
|
||||
if (!Yii::$app->user->isGuest) {
|
||||
return $this->goHome();
|
||||
}
|
||||
|
||||
$this->layout = 'blank';
|
||||
|
||||
$model = new LoginForm();
|
||||
if ($model->load(Yii::$app->request->post()) && $model->login()) {
|
||||
return $this->goBack();
|
||||
}
|
||||
|
||||
$model->password = '';
|
||||
|
||||
return $this->render('login', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout action.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function actionLogout()
|
||||
{
|
||||
Yii::$app->user->logout();
|
||||
|
||||
return $this->goHome();
|
||||
}
|
||||
}
|
||||
0
api/models/.gitkeep
Normal file
0
api/models/.gitkeep
Normal file
0
api/runtime/.gitignore
vendored
Normal file
0
api/runtime/.gitignore
vendored
Normal file
0
api/tests/_bootstrap.php
Normal file
0
api/tests/_bootstrap.php
Normal file
0
api/tests/_data/.gitignore
vendored
Normal file
0
api/tests/_data/.gitignore
vendored
Normal file
13
api/tests/_data/login_data.php
Normal file
13
api/tests/_data/login_data.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
return [
|
||||
[
|
||||
'username' => 'erau',
|
||||
'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI',
|
||||
// password_0
|
||||
'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne',
|
||||
'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490',
|
||||
'created_at' => '1392559490',
|
||||
'updated_at' => '1392559490',
|
||||
'email' => 'sfriesen@jenkins.info',
|
||||
],
|
||||
];
|
||||
0
api/tests/_output/.gitignore
vendored
Normal file
0
api/tests/_output/.gitignore
vendored
Normal file
0
api/tests/_support/.gitignore
vendored
Normal file
0
api/tests/_support/.gitignore
vendored
Normal file
0
api/tests/_support/FunctionalTester.php
Normal file
0
api/tests/_support/FunctionalTester.php
Normal file
0
api/tests/_support/UnitTester.php
Normal file
0
api/tests/_support/UnitTester.php
Normal file
5
api/tests/functional.suite.yml
Normal file
5
api/tests/functional.suite.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
suite_namespace: backend\tests\functional
|
||||
actor: FunctionalTester
|
||||
modules:
|
||||
enabled:
|
||||
- Yii2
|
||||
44
api/tests/functional/LoginCest.php
Normal file
44
api/tests/functional/LoginCest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace backend\tests\functional;
|
||||
|
||||
use backend\tests\FunctionalTester;
|
||||
use common\fixtures\UserFixture;
|
||||
|
||||
/**
|
||||
* Class LoginCest
|
||||
*/
|
||||
class LoginCest
|
||||
{
|
||||
/**
|
||||
* Load fixtures before db transaction begin
|
||||
* Called in _before()
|
||||
* @see \Codeception\Module\Yii2::_before()
|
||||
* @see \Codeception\Module\Yii2::loadFixtures()
|
||||
* @return array
|
||||
*/
|
||||
public function _fixtures()
|
||||
{
|
||||
return [
|
||||
'user' => [
|
||||
'class' => UserFixture::class,
|
||||
'dataFile' => codecept_data_dir() . 'login_data.php'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FunctionalTester $I
|
||||
*/
|
||||
public function loginUser(FunctionalTester $I)
|
||||
{
|
||||
$I->amOnRoute('/site/login');
|
||||
$I->fillField('Username', 'erau');
|
||||
$I->fillField('Password', 'password_0');
|
||||
$I->click('login-button');
|
||||
|
||||
$I->see('Logout (erau)', 'form button[type=submit]');
|
||||
$I->dontSeeLink('Login');
|
||||
$I->dontSeeLink('Signup');
|
||||
}
|
||||
}
|
||||
0
api/tests/functional/_bootstrap.php
Normal file
0
api/tests/functional/_bootstrap.php
Normal file
2
api/tests/unit.suite.yml
Normal file
2
api/tests/unit.suite.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
suite_namespace: backend\tests\unit
|
||||
actor: UnitTester
|
||||
0
api/tests/unit/_bootstrap.php
Normal file
0
api/tests/unit/_bootstrap.php
Normal file
0
api/views/layouts/blank.php
Normal file
0
api/views/layouts/blank.php
Normal file
0
api/views/site/error.php
Normal file
0
api/views/site/error.php
Normal file
53
api/views/site/index.php
Normal file
53
api/views/site/index.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/** @var yii\web\View $this */
|
||||
|
||||
$this->title = 'My Yii Application';
|
||||
?>
|
||||
<div class="site-index">
|
||||
|
||||
<div class="jumbotron text-center bg-transparent">
|
||||
<h1 class="display-4">Welcome!</h1>
|
||||
|
||||
<p class="lead">We are excited for you to get started.</p>
|
||||
|
||||
<p><a class="btn btn-lg btn-success" href="http://www.yiiframework.com">Get started with Yii</a></p>
|
||||
</div>
|
||||
|
||||
<div class="body-content">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<h2>Heading</h2>
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
|
||||
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur.</p>
|
||||
|
||||
<p><a class="btn btn-outline-secondary" href="http://www.yiiframework.com/doc/">Yii Documentation »</a></p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<h2>Heading</h2>
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
|
||||
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur.</p>
|
||||
|
||||
<p><a class="btn btn-outline-secondary" href="http://www.yiiframework.com/forum/">Yii Forum »</a></p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<h2>Heading</h2>
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
|
||||
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur.</p>
|
||||
|
||||
<p><a class="btn btn-outline-secondary" href="http://www.yiiframework.com/extensions/">Yii Extensions »</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
0
api/web/assets/.gitignore
vendored
Normal file
0
api/web/assets/.gitignore
vendored
Normal file
0
api/web/css/site.css
Normal file
0
api/web/css/site.css
Normal file
0
api/web/favicon.ico
Normal file
0
api/web/favicon.ico
Normal file
0
api/web/index-test.php
Normal file
0
api/web/index-test.php
Normal file
0
api/web/index.php
Normal file
0
api/web/index.php
Normal file
0
api/web/robots.txt
Normal file
0
api/web/robots.txt
Normal file
8
codeception.yml
Normal file
8
codeception.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
# global codeception file to run tests from all apps
|
||||
include:
|
||||
- common
|
||||
- frontend
|
||||
paths:
|
||||
output: console/runtime/output
|
||||
settings:
|
||||
colors: true
|
||||
15
common/codeception.yml
Normal file
15
common/codeception.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace: common\tests
|
||||
actor_suffix: Tester
|
||||
paths:
|
||||
tests: tests
|
||||
output: tests/_output
|
||||
data: tests/_data
|
||||
support: tests/_support
|
||||
bootstrap: _bootstrap.php
|
||||
settings:
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
modules:
|
||||
config:
|
||||
Yii2:
|
||||
configFile: 'config/codeception-local.php'
|
||||
19
common/components/PostmarkComponent.php
Normal file
19
common/components/PostmarkComponent.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace common\components;
|
||||
|
||||
use Postmark\PostmarkClient;
|
||||
use yii\base\Component;
|
||||
|
||||
class PostmarkComponent extends Component
|
||||
{
|
||||
/** @var PostmarkClient */
|
||||
public PostmarkClient $client;
|
||||
public mixed $serverToken;
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->client = new PostmarkClient($this->serverToken);
|
||||
}
|
||||
}
|
||||
469
common/components/SonarApiComponent.php
Normal file
469
common/components/SonarApiComponent.php
Normal file
@@ -0,0 +1,469 @@
|
||||
<?php
|
||||
|
||||
namespace common\components;
|
||||
|
||||
use common\jobs\EmailJob;
|
||||
use common\models\Account;
|
||||
use common\models\InvoiceItem;
|
||||
use common\models\SalesAgent;
|
||||
use common\models\Service;
|
||||
use Yii;
|
||||
use yii\helpers\Url;
|
||||
use yii\httpclient\Client;
|
||||
use yii\httpclient\Request;
|
||||
use yii\httpclient\RequestEvent;
|
||||
|
||||
class SonarApiComponent extends \yii\base\Component
|
||||
{
|
||||
public Client $client;
|
||||
public string $baseUrl;
|
||||
public string $bearerToken;
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->client = new Client([
|
||||
'baseUrl' => $this->baseUrl,
|
||||
'requestConfig' => [
|
||||
'format' => Client::FORMAT_JSON
|
||||
],
|
||||
'responseConfig' => [
|
||||
'format' => Client::FORMAT_JSON
|
||||
],
|
||||
]);
|
||||
|
||||
// Setup event for auth before each send
|
||||
$this->client->on(Request::EVENT_BEFORE_SEND, function (RequestEvent $event) {
|
||||
$event->request->addHeaders(['Authorization' => 'Bearer ' . $this->bearerToken]);
|
||||
});
|
||||
}
|
||||
|
||||
public function getAccounts(int $page = 1, int $limit = 100)
|
||||
{
|
||||
$data = [
|
||||
'form_params' => [
|
||||
'query' => 'query accountsWithSalesAgents(
|
||||
$paginator: Paginator,
|
||||
$search: [Search],
|
||||
$sorter: [Sorter]
|
||||
) {
|
||||
accounts(
|
||||
paginator: $paginator
|
||||
search: $search
|
||||
sorter: $sorter
|
||||
reverse_relation_filters: {
|
||||
relation: "custom_field_data",
|
||||
search: {
|
||||
integer_fields: {
|
||||
attribute: "custom_field_id",
|
||||
search_value: 12,
|
||||
operator: EQ
|
||||
}
|
||||
}
|
||||
}
|
||||
general_search_mode: ROOT_PLUS_RELATIONS
|
||||
account_status_id: 1
|
||||
) {
|
||||
entities {
|
||||
id
|
||||
name
|
||||
account_status {
|
||||
id
|
||||
name
|
||||
}
|
||||
account_services {
|
||||
entities {
|
||||
id
|
||||
quantity
|
||||
name_override
|
||||
price_override
|
||||
price_override_reason
|
||||
service {
|
||||
id
|
||||
name
|
||||
amount
|
||||
enabled
|
||||
application
|
||||
type
|
||||
}
|
||||
}
|
||||
page_info {
|
||||
page
|
||||
records_per_page
|
||||
total_count
|
||||
total_pages
|
||||
}
|
||||
}
|
||||
custom_field_data(custom_field_id:12) {
|
||||
entities {
|
||||
id
|
||||
custom_field_id
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
page_info {
|
||||
page
|
||||
records_per_page
|
||||
total_count
|
||||
total_pages
|
||||
}
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'paginator' => [
|
||||
'page' => $page,
|
||||
'records_per_page' => $limit
|
||||
],
|
||||
'search' => [],
|
||||
'sorter' => [
|
||||
[
|
||||
'attribute' => 'updated_at',
|
||||
'direction' => 'ASC',
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->createRequest()
|
||||
->setMethod('POST')
|
||||
->setData($data)
|
||||
->send();
|
||||
|
||||
$account = json_decode($response->getContent(), true);
|
||||
|
||||
return $account['form_params']['data'];
|
||||
}
|
||||
|
||||
public function getInvoices(string $startDate, string $endDate)
|
||||
{
|
||||
$page = 1;
|
||||
$limit = 100;
|
||||
$invoices = [];
|
||||
do {
|
||||
$data = [
|
||||
'form_params' => [
|
||||
'query' => 'query accountInvoice($paginator: Paginator, $search: [Search], $sorter: [Sorter]) {
|
||||
invoices(
|
||||
paginator: $paginator
|
||||
search: $search
|
||||
sorter: $sorter
|
||||
general_search_mode: ROOT_PLUS_RELATIONS
|
||||
) {
|
||||
entities {
|
||||
id
|
||||
account_id
|
||||
total_debits
|
||||
void
|
||||
remaining_due
|
||||
date
|
||||
due_date
|
||||
end_date
|
||||
delinquent
|
||||
debits {
|
||||
entities {
|
||||
id
|
||||
quantity
|
||||
service_id
|
||||
service_name
|
||||
amount
|
||||
}
|
||||
}
|
||||
credits {
|
||||
entities {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
page_info {
|
||||
page
|
||||
records_per_page
|
||||
total_count
|
||||
total_pages
|
||||
}
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'paginator' => [
|
||||
'page' => $page,
|
||||
'records_per_page' => $limit
|
||||
],
|
||||
'search' => [
|
||||
[
|
||||
'date_fields' => [
|
||||
['attribute' => 'date', 'search_value' => $startDate, 'operator' => 'GTE'],
|
||||
['attribute' => 'date', 'search_value' => $endDate, 'operator' => 'LTE'],
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
],
|
||||
'sorter' => [
|
||||
[
|
||||
'attribute' => 'updated_at',
|
||||
'direction' => 'ASC',
|
||||
]
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->createRequest()
|
||||
->setMethod('POST')
|
||||
->setData($data)
|
||||
->send();
|
||||
|
||||
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
$invoices = array_merge($invoices, $responseData['form_params']['data']['invoices']['entities']);
|
||||
$page++;
|
||||
} while ($page < ($responseData['form_params']['data']['invoices']['page_info']['total_pages'] + 1));
|
||||
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
public function getInvoice(int $invoiceId = 1)
|
||||
{
|
||||
$page = 1;
|
||||
$limit = 100;
|
||||
$data = [
|
||||
'form_params' => [
|
||||
'query' => 'query accountInvoice($paginator: Paginator, $search: [Search], $sorter: [Sorter]) {
|
||||
invoices(
|
||||
id: ' . $invoiceId . '
|
||||
paginator: $paginator
|
||||
search: $search
|
||||
sorter: $sorter
|
||||
general_search_mode: ROOT_PLUS_RELATIONS
|
||||
) {
|
||||
entities {
|
||||
id
|
||||
account_id
|
||||
total_debits
|
||||
void
|
||||
remaining_due
|
||||
date
|
||||
due_date
|
||||
end_date
|
||||
delinquent
|
||||
debits {
|
||||
entities {
|
||||
id
|
||||
quantity
|
||||
service_id
|
||||
service_name
|
||||
amount
|
||||
}
|
||||
}
|
||||
credits {
|
||||
entities {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
page_info {
|
||||
page
|
||||
records_per_page
|
||||
total_count
|
||||
total_pages
|
||||
}
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'paginator' => [
|
||||
'page' => $page,
|
||||
'records_per_page' => $limit
|
||||
],
|
||||
'search' => [],
|
||||
'sorter' => [
|
||||
[
|
||||
'attribute' => 'updated_at',
|
||||
'direction' => 'ASC',
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->createRequest()
|
||||
->setMethod('POST')
|
||||
->setData($data)
|
||||
->send();
|
||||
|
||||
$invoice = json_decode($response->getContent(), true);
|
||||
|
||||
return $invoice['form_params']['data']['invoices']['entities'][0];
|
||||
}
|
||||
|
||||
public function storeInvoices($invoices)
|
||||
{
|
||||
foreach ($invoices as $invoice) {
|
||||
$this->storeInvoice($invoice);
|
||||
}
|
||||
}
|
||||
|
||||
public function storeInvoice($invoice)
|
||||
{
|
||||
\Yii::debug($invoice);
|
||||
// $remainingDue is the Entire Invoice remaining to be paid amount, 0 = everything paid
|
||||
$remainingDue = $invoice['remaining_due'];
|
||||
// debits = charges on the account
|
||||
// credits = payments on the account
|
||||
|
||||
foreach ($invoice['debits']['entities'] as $i => $rawItem) {
|
||||
$invoiceItem = InvoiceItem::find()->where(['sonar_id' => (int)$rawItem['id']])->one();
|
||||
if (null === $invoiceItem) { // create new invoice item
|
||||
|
||||
$account = Account::findOne(['sonar_id' => (int)$invoice['account_id']]);
|
||||
$service = Service::findOne(['sonar_id' => (int)$rawItem['service_id']]);
|
||||
\Yii::debug($rawItem);
|
||||
if ($service && $account) {
|
||||
\Yii::debug($invoice);
|
||||
$payment = (isset($invoice['credits']['entities'][$i]['amount'])) ? $invoice['credits']['entities'][$i]['amount'] : 0;
|
||||
// @todo check payment - i think it is wrong to assume we have the same credits and debits ^ CGS
|
||||
$invoiceItem = new InvoiceItem([
|
||||
'sonar_id' => (int)$rawItem['id'],
|
||||
'account_id' => $account->id,
|
||||
'service_id' => $service->id,
|
||||
'name' => $rawItem['service_name'],
|
||||
'status' => InvoiceItem::STATUS_OPEN,
|
||||
'charge' => $rawItem['amount'],
|
||||
'payment' => $payment,
|
||||
'is_commissionable' => $service->hasCommission(),
|
||||
]);
|
||||
$invoiceItem->save();
|
||||
}
|
||||
}
|
||||
|
||||
// is the invoice item paid?
|
||||
if ($invoiceItem && $remainingDue == 0) {
|
||||
$invoiceItem->status = InvoiceItem::STATUS_PAYMENT_RECEIVED;
|
||||
$invoiceItem->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mapAccounts($accounts)
|
||||
{
|
||||
$mapped = [];
|
||||
$i = 0;
|
||||
$db = \Yii::$app->db;
|
||||
foreach ($accounts as $account) {
|
||||
$mapped[$i]['sonar_id'] = (int)$account['id'];
|
||||
$mapped[$i]['name'] = $account['name'];
|
||||
|
||||
/**
|
||||
* [
|
||||
* 'id' => '132'
|
||||
* 'quantity' => 1
|
||||
* 'name_override' => 'Bradley'
|
||||
* 'price_override' => 0
|
||||
* 'price_override_reason' => 'testing'
|
||||
* 'service' => [
|
||||
* 'id' => '10'
|
||||
* 'name' => 'Business Giga Speed Internet'
|
||||
* 'amount' => 18000
|
||||
* 'enabled' => true
|
||||
* 'application' => 'DEBIT'
|
||||
* 'type' => 'DATA'
|
||||
* ]
|
||||
* ]
|
||||
*/
|
||||
$mapped[$i]['services'] = []; // init empty array
|
||||
|
||||
foreach ($account['account_services']['entities'] as $key => $account_service) {
|
||||
$mapped[$i]['services'][$key]['sonar_id'] = (int)$account_service['service']['id'];
|
||||
$mapped[$i]['services'][$key]['name'] = (!empty($account_service['name_override'])) ? $account_service['name_override'] : $account_service['service']['name'];
|
||||
$mapped[$i]['services'][$key]['price'] = (!empty($account_service['price_override'])) ? $account_service['price_override'] : $account_service['service']['amount'];
|
||||
if ($account_service['service']['application'] === 'CREDIT') {
|
||||
$mapped[$i]['services'][$key]['price'] = -1 * $mapped[$i]['services'][$key]['price'];// store as a negative
|
||||
}
|
||||
|
||||
// set to 0 if null after credit
|
||||
if (null === $mapped[$i]['services'][$key]['price']) {
|
||||
$mapped[$i]['services'][$key]['price'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$name = $account['custom_field_data']['entities'][0]['value'];
|
||||
$salesAgent = SalesAgent::findOne(['name' => $name]);
|
||||
if (null === $salesAgent) {
|
||||
$salesAgent = new SalesAgent(['name' => $name]);
|
||||
$salesAgent->save();
|
||||
}
|
||||
|
||||
$mapped[$i]['sales_agent_id'] = $salesAgent->id;
|
||||
|
||||
$i++;
|
||||
}
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
public function storeAccounts()
|
||||
{
|
||||
$page = 1;
|
||||
do {
|
||||
$batch = $this->getAccounts($page, 100);
|
||||
$accounts = $this->mapAccounts($batch['accounts']['entities']);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$accountModel = Account::findOne(['sonar_id' => $account['sonar_id']]);
|
||||
if (null === $accountModel) {
|
||||
$accountModel = new Account([
|
||||
'sonar_id' => $account['sonar_id'],
|
||||
'name' => $account['name'],
|
||||
'sales_agent_id' => $account['sales_agent_id'],
|
||||
]);
|
||||
$accountModel->save();
|
||||
} else {
|
||||
//$accountModel->sonar_id = $account['sonar_id'];
|
||||
$accountModel->name = $account['name'];
|
||||
$accountModel->sales_agent_id = $account['sales_agent_id'];
|
||||
$accountModel->save();
|
||||
}
|
||||
|
||||
foreach ($account['services'] as $rawServiceData) {
|
||||
$serviceModel = Service::findOne(['sonar_id' => (int)$rawServiceData['sonar_id']]);
|
||||
if (null === $serviceModel) {
|
||||
$serviceModel = new Service([
|
||||
'sonar_id' => $rawServiceData['sonar_id'],
|
||||
'name' => $rawServiceData['name'],
|
||||
'price' => $rawServiceData['price'],
|
||||
'account_id' => $accountModel->id,
|
||||
'active' => 1, // @todo pull active state in from sonar api
|
||||
]);
|
||||
$serviceModel->save();
|
||||
} else {
|
||||
$serviceModel->commission = $serviceModel->getFormattedDollar($serviceModel->commission, false);
|
||||
$serviceModel->sonar_id = $rawServiceData['sonar_id'];
|
||||
$serviceModel->name = $rawServiceData['name'];
|
||||
$serviceModel->price = $rawServiceData['price'];
|
||||
$serviceModel->account_id = $accountModel->id;
|
||||
if (!empty($serviceModel->dirtyAttributes)) {
|
||||
if (isset($serviceModel->dirtyAttributes['price'])) {
|
||||
//Yii::$app->queue->push(new EmailJob([
|
||||
// 'templateAlias' => EmailJob::PRICE_CHANGE,
|
||||
// "email" => Yii::$app->params['adminEmail'],
|
||||
// 'templateModel' => [
|
||||
// "action_edit_url" => Yii::$app->urlManager->createAbsoluteUrl(
|
||||
// ['service/update', 'id' => $serviceModel->id]
|
||||
// ),
|
||||
// ]
|
||||
//]));
|
||||
}
|
||||
}
|
||||
$serviceModel->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$page++;
|
||||
} while ($page < ($batch['accounts']['page_info']['total_pages'] + 1));
|
||||
}
|
||||
|
||||
public function processInvoices(int $invoiceId)
|
||||
{
|
||||
dump($this->getInvoice($invoiceId));
|
||||
}
|
||||
|
||||
}
|
||||
4
common/config/.gitignore
vendored
Normal file
4
common/config/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
codeception-local.php
|
||||
main-local.php
|
||||
params-local.php
|
||||
test-local.php
|
||||
37
common/config/__autocomplete.php
Normal file
37
common/config/__autocomplete.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class only exists here for IDE (PHPStorm/Netbeans/...) autocompletion.
|
||||
* This file is never included anywhere.
|
||||
* Adjust this file to match classes configured in your application config, to enable IDE autocompletion for custom components.
|
||||
* Example: A property phpdoc can be added in `__Application` class as `@property \vendor\package\Rollbar|__Rollbar $rollbar` and adding a class in this file
|
||||
* ```php
|
||||
* // @property of \vendor\package\Rollbar goes here
|
||||
* class __Rollbar {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Yii {
|
||||
/**
|
||||
* @var \yii\web\Application|\yii\console\Application|__Application
|
||||
*/
|
||||
public static $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @property yii\rbac\DbManager $authManager
|
||||
* @property \Da\User\Model\User $user
|
||||
* @property \common\components\SonarApiComponent $sonar
|
||||
* @property \common\components\HubspotApiComponent $hubspot
|
||||
* @property \common\components\PostmarkComponent $postmark
|
||||
* @property \yii\queue\db\Queue $queue
|
||||
*
|
||||
*/
|
||||
class __Application {
|
||||
}
|
||||
|
||||
/**
|
||||
* @property app\models\User $identity
|
||||
*/
|
||||
class __WebUser {
|
||||
}
|
||||
5
common/config/bootstrap.php
Normal file
5
common/config/bootstrap.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
Yii::setAlias('@common', dirname(__DIR__));
|
||||
Yii::setAlias('@frontend', dirname(dirname(__DIR__)) . '/frontend');
|
||||
Yii::setAlias('@backend', dirname(dirname(__DIR__)) . '/api');
|
||||
Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');
|
||||
44
common/config/main.php
Normal file
44
common/config/main.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use common\components\PostmarkComponent;
|
||||
use common\components\SonarApiComponent;
|
||||
use common\components\HubspotApiComponent;
|
||||
use yii\caching\FileCache;
|
||||
use yii\queue\db\Queue;
|
||||
|
||||
$params = array_merge(
|
||||
require __DIR__ . '/params.php',
|
||||
require __DIR__ . '/params-local.php'
|
||||
);
|
||||
|
||||
return [
|
||||
'name' => $params['company_name'] . ' - ' . $params['product_name'],
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
|
||||
'components' => [
|
||||
'cache' => [
|
||||
'class' => FileCache::class,
|
||||
],
|
||||
'sonar' => [
|
||||
'class' => SonarApiComponent::class,
|
||||
'baseUrl' => $params['sonar.url'] . '/api/graphql',
|
||||
'bearerToken' => $params['sonar.bearerToken'],
|
||||
],
|
||||
'postmark' => [
|
||||
'class' => PostmarkComponent::class,
|
||||
'serverToken' => $params['postmark.serverToken'],
|
||||
],
|
||||
'authManager' => [
|
||||
'class' => 'yii\rbac\DbManager',
|
||||
],
|
||||
'urlManager' => [
|
||||
'enablePrettyUrl' => true,
|
||||
'showScriptName' => false,
|
||||
'rules' => [
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
14
common/config/params.php
Normal file
14
common/config/params.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
return [
|
||||
'adminEmail' => 'admin@example.com',
|
||||
'supportEmail' => 'support@example.com',
|
||||
'senderEmail' => 'noreply@example.com',
|
||||
'senderName' => 'Example.com mailer',
|
||||
'user.passwordResetTokenExpire' => 3600,
|
||||
'user.passwordMinLength' => 8,
|
||||
'sonar.url' => 'https://yourname.sonar.software',
|
||||
'sonar.bearerToken' => '',
|
||||
'postmark.serverToken' => 'postmark-server-key',
|
||||
'postmark.messageStream' => 'outbound',
|
||||
'sentry.dsn' => 'https://asdf@o4507934844780544.ingest.us.sentry.io/4508006893158400',
|
||||
];
|
||||
11
common/config/test.php
Normal file
11
common/config/test.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
return [
|
||||
'id' => 'app-common-tests',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'components' => [
|
||||
'user' => [
|
||||
'class' => \yii\web\User::class,
|
||||
'identityClass' => 'common\models\User',
|
||||
],
|
||||
],
|
||||
];
|
||||
11
common/fixtures/AuthAssignmentFixture.php
Normal file
11
common/fixtures/AuthAssignmentFixture.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace common\fixtures;
|
||||
|
||||
use yii\test\ActiveFixture;
|
||||
|
||||
class AuthAssignmentFixture extends ActiveFixture
|
||||
{
|
||||
public $tableName = '{{%auth_assignment}}';
|
||||
public $depends = [UserFixture::class];
|
||||
}
|
||||
11
common/fixtures/UserFixture.php
Normal file
11
common/fixtures/UserFixture.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace common\fixtures;
|
||||
|
||||
use yii\test\ActiveFixture;
|
||||
|
||||
class UserFixture extends ActiveFixture
|
||||
{
|
||||
public $modelClass = 'common\models\User';
|
||||
public $depends = [];
|
||||
}
|
||||
12
common/fixtures/data/auth_assignment.php
Normal file
12
common/fixtures/data/auth_assignment.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'item_name' => 'admin',
|
||||
'user_id' => 1,
|
||||
],
|
||||
[
|
||||
'item_name' => 'user',
|
||||
'user_id' => 2,
|
||||
],
|
||||
];
|
||||
22
common/fixtures/data/user.php
Normal file
22
common/fixtures/data/user.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
//password
|
||||
'password_hash' => '$2y$13$k2hQ8aV/z6V0Y7pRbq1ufOUSaJI7EhNvvTUIoj2s/rxAmgyY95KPa',
|
||||
'email' => 'admin@example.com',
|
||||
'auth_key' => '1',
|
||||
'status' => '1',
|
||||
'created_at' => '1402312317',
|
||||
'updated_at' => '1402312317',
|
||||
],
|
||||
[
|
||||
//password
|
||||
'password_hash' => '$2y$13$k2hQ8aV/z6V0Y7pRbq1ufOUSaJI7EhNvvTUIoj2s/rxAmgyY95KPa',
|
||||
'email' => 'user@example.com',
|
||||
'auth_key' => '1',
|
||||
'status' => '1',
|
||||
'created_at' => '1402312317',
|
||||
'updated_at' => '1402312317',
|
||||
],
|
||||
];
|
||||
65
common/jobs/EmailJob.php
Normal file
65
common/jobs/EmailJob.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace common\jobs;
|
||||
|
||||
use Yii;
|
||||
use yii\base\BaseObject;
|
||||
use yii\queue\RetryableJobInterface;
|
||||
|
||||
class EmailJob extends BaseObject implements RetryableJobInterface
|
||||
{
|
||||
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const TTR = 600; // in seconds
|
||||
|
||||
public const PASSWORD_RESET = 'password-reset';
|
||||
public const PASSWORD_HAS_BEEN_RESET = 'password-has-been-reset';
|
||||
public const WELCOME_EMAIL = 'welcome-email';
|
||||
public const VERIFY_EMAIL = 'verify-email';
|
||||
public const ADMIN_NOTIFY = 'admin-new-user';
|
||||
public const PRICE_CHANGE = 'price-change';
|
||||
public const PAYOUT_NOTIFY = 'payout-notify';
|
||||
public array $templateModel;
|
||||
public string $email;
|
||||
public string $templateAlias;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function execute($queue)
|
||||
{
|
||||
// Merge these values which are always on emails
|
||||
$this->templateModel = array_merge($this->templateModel, [
|
||||
"product" => Yii::$app->params['product_name'],
|
||||
"product_name" => Yii::$app->params['product_name'],
|
||||
"support_url" => Yii::$app->params['support_url'],
|
||||
"product_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/index']),
|
||||
"company_name" => Yii::$app->params['company_name'],
|
||||
"company_address" => Yii::$app->params['company_address'],
|
||||
]);
|
||||
|
||||
Yii::$app->postmark->client->sendEmailWithTemplate(
|
||||
Yii::$app->params['supportEmail'],
|
||||
$this->email,
|
||||
$this->templateAlias,
|
||||
$this->templateModel,
|
||||
messageStream: Yii::$app->params['postmark.messageStream']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTtr()
|
||||
{
|
||||
return self::TTR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function canRetry($attempt, $error)
|
||||
{
|
||||
return ($attempt < self::MAX_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
13
common/models/AuthAssignment.php
Normal file
13
common/models/AuthAssignment.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
class AuthAssignment extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return '{{%auth_assignment}}';
|
||||
}
|
||||
}
|
||||
79
common/models/LoginForm.php
Normal file
79
common/models/LoginForm.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
* Login form
|
||||
*/
|
||||
class LoginForm extends Model
|
||||
{
|
||||
public $email;
|
||||
public $password;
|
||||
public $rememberMe = true;
|
||||
|
||||
private $_user;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// username and password are both required
|
||||
[['email', 'password'], 'required'],
|
||||
// rememberMe must be a boolean value
|
||||
['rememberMe', 'boolean'],
|
||||
// password is validated by validatePassword()
|
||||
['password', 'validatePassword'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the password.
|
||||
* This method serves as the inline validation for password.
|
||||
*
|
||||
* @param string $attribute the attribute currently being validated
|
||||
* @param array $params the additional name-value pairs given in the rule
|
||||
*/
|
||||
public function validatePassword($attribute, $params)
|
||||
{
|
||||
if (!$this->hasErrors()) {
|
||||
$user = $this->getUser();
|
||||
if (!$user || !$user->validatePassword($this->password)) {
|
||||
$this->addError($attribute, 'Incorrect email or password.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user using the provided email and password.
|
||||
*
|
||||
* @return bool whether the user is logged in successfully
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if ($this->validate()) {
|
||||
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by [[email]]
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
protected function getUser()
|
||||
{
|
||||
if ($this->_user === null) {
|
||||
$this->_user = User::findByEmail($this->email);
|
||||
}
|
||||
|
||||
return $this->_user;
|
||||
}
|
||||
}
|
||||
61
common/models/Meal.php
Normal file
61
common/models/Meal.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace common\models;
|
||||
|
||||
/**
|
||||
* This is the model class for table "meal".
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $file_name
|
||||
* @property int $calories
|
||||
* @property int $protein
|
||||
* @property int $fat
|
||||
* @property int $carbohydrates
|
||||
* @property int $fiber
|
||||
* @property int $meal
|
||||
* @property int $created_at
|
||||
* @property int $updated_at
|
||||
*/
|
||||
class Meal extends \yii\db\ActiveRecord
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return 'meal';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['file_name', 'calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'required'],
|
||||
[['calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'integer'],
|
||||
[['file_name'], 'string', 'max' => 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'file_name' => 'File Name',
|
||||
'calories' => 'Calories',
|
||||
'protein' => 'Protein',
|
||||
'fat' => 'Fat',
|
||||
'carbohydrates' => 'Carbohydrates',
|
||||
'fiber' => 'Fiber',
|
||||
'meal' => 'Meal',
|
||||
'created_at' => 'Created At',
|
||||
'updated_at' => 'Updated At',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
332
common/models/User.php
Normal file
332
common/models/User.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use common\jobs\EmailJob;
|
||||
use Yii;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\db\AfterSaveEvent;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
/**
|
||||
* User model
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $password_hash
|
||||
* @property string $password_reset_token
|
||||
* @property string $verification_token
|
||||
* @property string $email
|
||||
* @property string $first_name
|
||||
* @property string $name
|
||||
* @property string $auth_key
|
||||
* @property integer $status
|
||||
* @property integer $sales_agent_id
|
||||
* @property integer $created_at
|
||||
* @property integer $updated_at
|
||||
* @property string $password write-only password
|
||||
*/
|
||||
class User extends ActiveRecord implements IdentityInterface
|
||||
{
|
||||
// User statuses
|
||||
public const STATUS_UNVERIFIED = 10;
|
||||
public const STATUS_INACTIVE = 0;
|
||||
public const STATUS_ACTIVE = 1;
|
||||
public const STATUS_VERIFIED = 2;
|
||||
|
||||
public const PAYOUT_INTERVAL_MONTHLY = 0;
|
||||
public array $userStatusArray;
|
||||
public string $role = '';
|
||||
public bool $welcomeEmailSent = false;
|
||||
public string $firstName;
|
||||
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->userStatusArray = [
|
||||
self::STATUS_UNVERIFIED => Yii::t('app', 'Unverified'),
|
||||
self::STATUS_INACTIVE => Yii::t('app', 'Inactive'),
|
||||
self::STATUS_ACTIVE => Yii::t('app', 'Active'),
|
||||
self::STATUS_VERIFIED => Yii::t('app', 'Verified (not active)'),
|
||||
];
|
||||
|
||||
// register event
|
||||
$this->on(self::EVENT_AFTER_INSERT, [$this, 'emailTrigger']);
|
||||
$this->on(self::EVENT_AFTER_UPDATE, [$this, 'emailTrigger']);
|
||||
}
|
||||
|
||||
|
||||
public function emailTrigger(AfterSaveEvent $event)
|
||||
{
|
||||
if ($event->sender->status == self::STATUS_ACTIVE && !$event->sender->welcome_email_sent) {
|
||||
Yii::$app->queue->push(new EmailJob([
|
||||
'templateAlias' => EmailJob::WELCOME_EMAIL,
|
||||
'email' => $event->sender->email,
|
||||
'templateModel' => [
|
||||
"name" => $event->sender->first_name,
|
||||
"action_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/login']),
|
||||
]
|
||||
]));
|
||||
|
||||
$event->sender->welcome_email_sent = true;
|
||||
$event->sender->save(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return '{{%user}}';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['status', 'default', 'value' => self::STATUS_UNVERIFIED],
|
||||
[['email'], 'email'],
|
||||
[['email'], 'unique'],
|
||||
[['sales_agent_id', 'created_at', 'updated_at'], 'integer'],
|
||||
[
|
||||
'sales_agent_id',
|
||||
'required',
|
||||
'when' => function ($model) {
|
||||
return $model->role === 'sales-agent';
|
||||
},
|
||||
'whenClient' => "function (attribute, value) {
|
||||
return $('#role').val() == 'sales-agent';
|
||||
}"
|
||||
],
|
||||
[
|
||||
'status',
|
||||
'in',
|
||||
'range' => [
|
||||
self::STATUS_ACTIVE,
|
||||
self::STATUS_UNVERIFIED,
|
||||
self::STATUS_VERIFIED,
|
||||
self::STATUS_INACTIVE,
|
||||
]
|
||||
],
|
||||
[['role'], 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function afterSave($insert, $changedAttributes)
|
||||
{
|
||||
$auth = Yii::$app->authManager;
|
||||
|
||||
// delete exiting roles if set
|
||||
$auth->revokeAll($this->id);
|
||||
// assign new role
|
||||
if (!empty($this->role)) {
|
||||
$auth->assign($auth->getRole($this->role), $this->id);
|
||||
}
|
||||
|
||||
parent::afterSave($insert, $changedAttributes);
|
||||
}
|
||||
|
||||
public function afterFind()
|
||||
{
|
||||
$rolesAssignedToUser = Yii::$app->authManager->getRolesByUser($this->id);
|
||||
// we only use one role for the user
|
||||
if (!empty($rolesAssignedToUser)) {
|
||||
$this->role = array_key_first($rolesAssignedToUser);
|
||||
}
|
||||
|
||||
parent::afterFind();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
TimestampBehavior::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names for dropdown lists
|
||||
*
|
||||
* @param $dropdown
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getStatusName($dropdown = false)
|
||||
{
|
||||
return $dropdown ? $this->userStatusArray : $this->userStatusArray[$this->status];
|
||||
}
|
||||
|
||||
public function getAuthAssignment()
|
||||
{
|
||||
return $this->hasOne(\common\models\AuthAssignment::class, ['user_id' => 'id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by email
|
||||
*
|
||||
* @param string $email
|
||||
* @return ActiveRecord|array|null
|
||||
*/
|
||||
public static function findByEmail($email)
|
||||
{
|
||||
return static::findOne(['email' => $email, 'status' => self::STATUS_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null)
|
||||
{
|
||||
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by password reset token
|
||||
*
|
||||
* @param string $token password reset token
|
||||
* @return ActiveRecord|array|null
|
||||
*/
|
||||
public static function findByPasswordResetToken($token)
|
||||
{
|
||||
if (!static::isPasswordResetTokenValid($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::findOne(['password_reset_token' => $token, 'status' => self::STATUS_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by verification email token
|
||||
*
|
||||
* @param string $token verify email token
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByVerificationToken($token)
|
||||
{
|
||||
return static::findOne([
|
||||
'verification_token' => $token,
|
||||
'status' => self::STATUS_UNVERIFIED
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if password reset token is valid
|
||||
*
|
||||
* @param string $token password reset token
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPasswordResetTokenValid($token)
|
||||
{
|
||||
if (empty($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = (int)substr($token, strrpos($token, '_') + 1);
|
||||
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
|
||||
return $timestamp + $expire >= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->getPrimaryKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthKey()
|
||||
{
|
||||
return $this->auth_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAuthKey($authKey)
|
||||
{
|
||||
return $this->getAuthKey() === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @return bool if password provided is valid for current user
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return Yii::$app->security->validatePassword($password, $this->password_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates password hash from password and sets it to the model
|
||||
*
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates "remember me" authentication key
|
||||
*/
|
||||
public function generateAuthKey()
|
||||
{
|
||||
$this->auth_key = Yii::$app->security->generateRandomString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new password reset token
|
||||
*/
|
||||
public function generatePasswordResetToken()
|
||||
{
|
||||
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new token for email verification
|
||||
*/
|
||||
public function generateEmailVerificationToken()
|
||||
{
|
||||
$this->verification_token = Yii::$app->security->generateRandomString() . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes password reset token
|
||||
*/
|
||||
public function removePasswordResetToken()
|
||||
{
|
||||
$this->password_reset_token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets query for [[SalesAgent]].
|
||||
*
|
||||
* @return \yii\db\ActiveQuery|yii\db\ActiveQuery
|
||||
*/
|
||||
public function getSalesAgent()
|
||||
{
|
||||
return $this->hasOne(SalesAgent::class, ['id' => 'sales_agent_id']);
|
||||
}
|
||||
}
|
||||
75
common/models/search/User.php
Normal file
75
common/models/search/User.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace common\models\search;
|
||||
|
||||
use common\models\User as UserModel;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\data\ActiveDataProvider;
|
||||
use common\models\SalesAgent;
|
||||
|
||||
|
||||
class User extends UserModel
|
||||
{
|
||||
public string $role = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['id', 'status'], 'integer'],
|
||||
[['email', 'first_name', 'role'], 'safe'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates data provider instance with search query applied
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return ActiveDataProvider
|
||||
*/
|
||||
public function search($params)
|
||||
{
|
||||
$query = UserModel::find()
|
||||
->joinWith('authAssignment', 'salesAgent');
|
||||
|
||||
$dataProvider = new ActiveDataProvider([
|
||||
'query' => $query,
|
||||
]);
|
||||
|
||||
$dataProvider->sort->attributes['salesAgentName'] = [
|
||||
'asc' => ['sales_agent_id' => SORT_ASC],
|
||||
'desc' => ['sales_agent_id' => SORT_DESC],
|
||||
];
|
||||
|
||||
$dataProvider->sort->attributes['role'] = [
|
||||
'asc' => ['auth_assignment.item_name' => SORT_ASC],
|
||||
'desc' => ['auth_assignment.item_name' => SORT_DESC],
|
||||
];
|
||||
|
||||
$this->load($params);
|
||||
|
||||
if (!$this->validate()) {
|
||||
return $dataProvider;
|
||||
}
|
||||
|
||||
if (!empty($this->salesAgentName)) {
|
||||
$salesAgent = SalesAgent::find()->where(['name' => $this->salesAgentName])->one();
|
||||
$this->sales_agent_id = $salesAgent ? $salesAgent->id : null;
|
||||
}
|
||||
|
||||
$query->andFilterWhere([
|
||||
'id' => $this->id,
|
||||
'status' => $this->status,
|
||||
'sales_agent_id' => $this->sales_agent_id,
|
||||
'auth_assignment.item_name' => $this->role
|
||||
]);
|
||||
|
||||
$query->andFilterWhere(['like', 'email', $this->email]);
|
||||
|
||||
return $dataProvider;
|
||||
}
|
||||
}
|
||||
16
common/rbac/SalesAgentRule.php
Normal file
16
common/rbac/SalesAgentRule.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace common\rbac;
|
||||
|
||||
class SalesAgentRule extends \yii\rbac\Rule
|
||||
{
|
||||
public $name = 'assignedToSalesAgent';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function execute($user, $item, $params)
|
||||
{
|
||||
return isset($params['post']) && $params['post']->sales_agent_id == $user;
|
||||
}
|
||||
}
|
||||
9
common/tests/_bootstrap.php
Normal file
9
common/tests/_bootstrap.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
defined('YII_APP_BASE_PATH') or define('YII_APP_BASE_PATH', __DIR__.'/../../');
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||
require __DIR__ . '/../config/bootstrap.php';
|
||||
23
common/tests/_data/user.php
Normal file
23
common/tests/_data/user.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'auth_key' => 'HP187Mvq7Mmm3CTU80dLkGmni_FUH_lR',
|
||||
//password_0
|
||||
'password_hash' => '$2y$13$.NVUBcghbQPiEcBh4LmI7.ctT3FDofwpgrKIAfwWvsr.wBM0l6Y7u',
|
||||
'password_reset_token' => 'ExzkCOaYc1L8IOBs4wdTGGbgNiG3Wz1I_1402312317',
|
||||
'created_at' => '1402312317',
|
||||
'updated_at' => '1402312317',
|
||||
'email' => 'nicole.paucek@schultz.info',
|
||||
],
|
||||
[
|
||||
//password
|
||||
'password_hash' => '$2y$13$k2hQ8aV/z6V0Y7pRbq1ufOUSaJI7EhNvvTUIoj2s/rxAmgyY95KPa',
|
||||
'email' => 'admin@example.com',
|
||||
'auth_key' => '1',
|
||||
'status' => '1',
|
||||
'sales_agent_id' => '0',
|
||||
'created_at' => '1402312317',
|
||||
'updated_at' => '1402312317',
|
||||
],
|
||||
];
|
||||
2
common/tests/_output/.gitignore
vendored
Normal file
2
common/tests/_output/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
1
common/tests/_support/.gitignore
vendored
Normal file
1
common/tests/_support/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_generated
|
||||
26
common/tests/_support/UnitTester.php
Normal file
26
common/tests/_support/UnitTester.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace common\tests;
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void verify($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class UnitTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\UnitTesterActions;
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
||||
7
common/tests/unit.suite.yml
Normal file
7
common/tests/unit.suite.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
suite_namespace: common\tests\unit
|
||||
actor: UnitTester
|
||||
bootstrap: false
|
||||
modules:
|
||||
enabled:
|
||||
- Yii2:
|
||||
part: fixtures
|
||||
67
common/tests/unit/models/LoginFormTest.php
Normal file
67
common/tests/unit/models/LoginFormTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace unit\models;
|
||||
|
||||
use Yii;
|
||||
use common\models\LoginForm;
|
||||
use common\fixtures\UserFixture;
|
||||
|
||||
/**
|
||||
* Login form test
|
||||
*/
|
||||
class LoginFormTest extends \Codeception\Test\Unit
|
||||
{
|
||||
/**
|
||||
* @var \common\tests\UnitTester
|
||||
*/
|
||||
protected $tester;
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function _fixtures()
|
||||
{
|
||||
return [
|
||||
'user' => [
|
||||
'class' => UserFixture::class,
|
||||
'dataFile' => codecept_data_dir() . 'user.php'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function testLoginNoUser()
|
||||
{
|
||||
$model = new LoginForm([
|
||||
'email' => 'not_existing_username',
|
||||
'password' => 'not_existing_password',
|
||||
]);
|
||||
|
||||
verify($model->login())->false();
|
||||
verify(Yii::$app->user->isGuest)->true();
|
||||
}
|
||||
|
||||
public function testLoginWrongPassword()
|
||||
{
|
||||
$model = new LoginForm([
|
||||
'email' => 'nicole.paucek@schultz.info',
|
||||
'password' => 'wrong_password',
|
||||
]);
|
||||
|
||||
verify($model->login())->false();
|
||||
verify( $model->errors)->arrayHasKey('password');
|
||||
verify(Yii::$app->user->isGuest)->true();
|
||||
}
|
||||
|
||||
public function testLoginCorrect()
|
||||
{
|
||||
$model = new LoginForm([
|
||||
'email' => 'admin@example.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
verify($model->login())->true();
|
||||
verify($model->errors)->arrayHasNotKey('password');
|
||||
verify(Yii::$app->user->isGuest)->false();
|
||||
}
|
||||
}
|
||||
45
common/tests/unit/traits/FormattedDollarTraitTest.php
Normal file
45
common/tests/unit/traits/FormattedDollarTraitTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace unit\traits;
|
||||
|
||||
use common\traits\FormattedDollarTrait;
|
||||
use Yii;
|
||||
|
||||
use function PHPUnit\Framework\assertEquals;
|
||||
|
||||
/**
|
||||
* Formatted dollar trait test
|
||||
*/
|
||||
class FormattedDollarTraitTest extends \Codeception\Test\Unit
|
||||
{
|
||||
/**
|
||||
* @dataProvider floatDataProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testConvertToCents($test, $expected)
|
||||
{
|
||||
$mock = $this->getMockForTrait(FormattedDollarTrait::class);
|
||||
|
||||
$this->assertEquals($expected, $mock->convertToCents($test));
|
||||
}
|
||||
|
||||
public function floatDataProvider()
|
||||
{
|
||||
return [
|
||||
[12.445, 1244],
|
||||
[-13.678901234, -1367],
|
||||
["-10.4", -1040],
|
||||
["-10", -1000],
|
||||
["11.445", 1144],
|
||||
["533.3.3533.11,445", 533335331144],
|
||||
["1,40032,0030.445", 140032003044],
|
||||
[124.99, 12499],
|
||||
[-1.4, -140],
|
||||
[14, 1400],
|
||||
[.99, 99],
|
||||
[2.3, 230],
|
||||
[-30, -3000],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
76
common/widgets/Alert.php
Normal file
76
common/widgets/Alert.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace common\widgets;
|
||||
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Alert widget renders a message from session flash. All flash messages are displayed
|
||||
* in the sequence they were assigned using setFlash. You can set message as following:
|
||||
*
|
||||
* ```php
|
||||
* Yii::$app->session->setFlash('error', 'This is the message');
|
||||
* Yii::$app->session->setFlash('success', 'This is the message');
|
||||
* Yii::$app->session->setFlash('info', 'This is the message');
|
||||
* ```
|
||||
*
|
||||
* Multiple messages could be set as follows:
|
||||
*
|
||||
* ```php
|
||||
* Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']);
|
||||
* ```
|
||||
*
|
||||
* @author Kartik Visweswaran <kartikv2@gmail.com>
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
*/
|
||||
class Alert extends \yii\bootstrap5\Widget
|
||||
{
|
||||
/**
|
||||
* @var array the alert types configuration for the flash messages.
|
||||
* This array is setup as $key => $value, where:
|
||||
* - key: the name of the session flash variable
|
||||
* - value: the bootstrap alert type (i.e. danger, success, info, warning)
|
||||
*/
|
||||
public $alertTypes = [
|
||||
'error' => 'alert-danger',
|
||||
'danger' => 'alert-danger',
|
||||
'success' => 'alert-success',
|
||||
'info' => 'alert-info',
|
||||
'warning' => 'alert-warning'
|
||||
];
|
||||
/**
|
||||
* @var array the options for rendering the close button tag.
|
||||
* Array will be passed to [[\yii\bootstrap\Alert::closeButton]].
|
||||
*/
|
||||
public $closeButton = [];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$session = Yii::$app->session;
|
||||
$flashes = $session->getAllFlashes();
|
||||
$appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : '';
|
||||
|
||||
foreach ($flashes as $type => $flash) {
|
||||
if (!isset($this->alertTypes[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ((array) $flash as $i => $message) {
|
||||
echo \yii\bootstrap5\Alert::widget([
|
||||
'body' => $message,
|
||||
'closeButton' => $this->closeButton,
|
||||
'options' => array_merge($this->options, [
|
||||
'id' => $this->getId() . '-' . $type . '-' . $i,
|
||||
'class' => $this->alertTypes[$type] . $appendClass,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$session->removeFlash($type);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
composer.json
Normal file
57
composer.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"license": "proprietary",
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"ext-json": "*",
|
||||
"yiisoft/yii2": "~2.0.45",
|
||||
"yiisoft/yii2-bootstrap5": "@dev",
|
||||
"yiisoft/yii2-authclient": "^2.2",
|
||||
"yiisoft/yii2-httpclient": "~2.0",
|
||||
"cgsmith/yii2-flatpickr-widget": "~1.1",
|
||||
"yiisoft/yii2-queue": "^2.3",
|
||||
"wildbit/postmark-php": "^6.0",
|
||||
"donatj/phpuseragentparser": "^1.9",
|
||||
"twbs/bootstrap-icons": "^1.11",
|
||||
"sentry/sentry": "^4.9",
|
||||
"kartik-v/yii2-widget-select2": "^2.2",
|
||||
"kartik-v/yii2-grid": "dev-master",
|
||||
"kartik-v/yii2-editable": "^1.8",
|
||||
"kartik-v/yii2-icons": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^5.1",
|
||||
"codeception/lib-innerbrowser": "^3.0 || ^1.1",
|
||||
"codeception/module-asserts": "^3.0 || ^1.1",
|
||||
"codeception/module-filesystem": "^3.0 || ^1.1",
|
||||
"codeception/module-yii2": "^1.1",
|
||||
"codeception/specify": "^2.0",
|
||||
"codeception/verify": "^3.0",
|
||||
"deployer/deployer": "^7.4",
|
||||
"phpunit/phpunit": "~9.5.0",
|
||||
"symfony/browser-kit": "^6.0 || >=2.7 <=4.2.4",
|
||||
"yiisoft/yii2-debug": "~2.1.0",
|
||||
"yiisoft/yii2-faker": "~2.0.0",
|
||||
"yiisoft/yii2-gii": "~2.2.0"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"yiisoft/yii2-composer": true
|
||||
},
|
||||
"process-timeout": 1800,
|
||||
"fxp-asset": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"phpunit"
|
||||
]
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
6917
composer.lock
generated
Normal file
6917
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
console/config/.gitignore
vendored
Normal file
3
console/config/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
main-local.php
|
||||
params-local.php
|
||||
test-local.php
|
||||
2
console/config/bootstrap.php
Normal file
2
console/config/bootstrap.php
Normal file
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
Yii::setAlias('@console', dirname(__DIR__));
|
||||
58
console/config/main.php
Normal file
58
console/config/main.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use yii\console\controllers\FixtureController;
|
||||
use yii\console\controllers\MigrateController;
|
||||
use yii\log\FileTarget;
|
||||
use function Sentry\init;
|
||||
|
||||
$params = array_merge(
|
||||
require __DIR__ . '/../../common/config/params.php',
|
||||
require __DIR__ . '/../../common/config/params-local.php',
|
||||
require __DIR__ . '/params.php',
|
||||
require __DIR__ . '/params-local.php'
|
||||
);
|
||||
|
||||
init([
|
||||
'dsn' => $params['sentry.dsn'],
|
||||
// Specify a fixed sample rate
|
||||
'traces_sample_rate' => 1.0,
|
||||
// Set a sampling rate for profiling - this is relative to traces_sample_rate
|
||||
'profiles_sample_rate' => 1.0,
|
||||
]);
|
||||
|
||||
return [
|
||||
'id' => 'app-console',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log', 'queue'],
|
||||
'controllerNamespace' => 'console\controllers',
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'controllerMap' => [
|
||||
'fixture' => [
|
||||
'class' => FixtureController::class,
|
||||
'namespace' => 'common\fixtures',
|
||||
],
|
||||
'migrate' => [
|
||||
'class' => MigrateController::class,
|
||||
'newFileOwnership' => '1000:1000', # Default WSL user id
|
||||
'newFileMode' => 0660,
|
||||
'migrationPath' => [
|
||||
'@app/migrations',
|
||||
'@yii/rbac/migrations',
|
||||
],
|
||||
],
|
||||
],
|
||||
'components' => [
|
||||
'log' => [
|
||||
'targets' => [
|
||||
[
|
||||
'class' => FileTarget::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
5
console/config/params.php
Normal file
5
console/config/params.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'adminEmail' => 'admin@example.com',
|
||||
];
|
||||
4
console/config/test.php
Normal file
4
console/config/test.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
96
console/controllers/CronController.php
Executable file
96
console/controllers/CronController.php
Executable file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace console\controllers;
|
||||
|
||||
use yii\console\{Controller, ExitCode};
|
||||
|
||||
// To create/edit crontab file: crontab -e
|
||||
// To list: crontab -l
|
||||
// // m h dom mon dow command
|
||||
// */5 * * * * /var/www/html/yii cron/frequent
|
||||
// */15 * * * * /var/www/html/yii cron/quarter
|
||||
// */30 * * * * /var/www/html/yii cron/halfhourly
|
||||
// 0 * * * * /var/www/html/yii cron/hourly
|
||||
// 15 1 * * * /var/www/html/yii cron/overnight
|
||||
// 15 3 * * 5 /var/www/html/yii cron/weekly
|
||||
|
||||
/**
|
||||
* Class CronController
|
||||
*
|
||||
* @package console\controllers
|
||||
*/
|
||||
class CronController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @var boolean whether to run the command interactively.
|
||||
*/
|
||||
public $interactive = false;
|
||||
|
||||
/**
|
||||
* Action Index
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionIndex()
|
||||
{
|
||||
$this->stdout("Yes, service cron is running\n");
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Frequent
|
||||
* Called every five minutes
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionFrequent()
|
||||
{
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Quarter
|
||||
* Called every fifteen minutes
|
||||
*
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionQuarter()
|
||||
{
|
||||
//
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Half Hourly
|
||||
* Called every 30 minutes
|
||||
*
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionHalfhourly()
|
||||
{
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Hourly
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionHourly()
|
||||
{
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Overnight
|
||||
* Called every night
|
||||
*
|
||||
* @return int exit code
|
||||
*/
|
||||
public function actionOvernight()
|
||||
{
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
}
|
||||
45
console/migrations/m221203_160610_create_user_table.php
Normal file
45
console/migrations/m221203_160610_create_user_table.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use common\models\User;
|
||||
use yii\db\Migration;
|
||||
|
||||
/**
|
||||
* Handles the creation of table `{{%user}}`.
|
||||
*/
|
||||
class m221203_160610_create_user_table extends Migration
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeUp()
|
||||
{
|
||||
$tableOptions = null;
|
||||
if ($this->db->driverName === 'mysql') {
|
||||
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
|
||||
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
|
||||
}
|
||||
|
||||
$this->createTable('{{%user}}', [
|
||||
'id' => $this->primaryKey(),
|
||||
'auth_key' => $this->string(32)->notNull(),
|
||||
'password_hash' => $this->string()->notNull(),
|
||||
'password_reset_token' => $this->string()->unique(),
|
||||
'verification_token' => $this->string()->defaultValue(null),
|
||||
'first_name' => $this->string(64),
|
||||
'email' => $this->string()->notNull()->unique(),
|
||||
'status' => $this->smallInteger()->notNull()->defaultValue(User::STATUS_UNVERIFIED),
|
||||
'welcome_email_sent' => $this->boolean()->defaultValue(false),
|
||||
'created_at' => $this->integer()->notNull(),
|
||||
'updated_at' => $this->integer()->notNull(),
|
||||
|
||||
], $tableOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeDown()
|
||||
{
|
||||
$this->dropTable('{{%user}}');
|
||||
}
|
||||
}
|
||||
87
console/migrations/m230120_214209_init_rbac.php
Normal file
87
console/migrations/m230120_214209_init_rbac.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
use yii\db\Migration;
|
||||
|
||||
/**
|
||||
* Class m230120_214209_init_rbac
|
||||
*/
|
||||
class m230120_214209_init_rbac extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$auth = Yii::$app->authManager;
|
||||
|
||||
/**
|
||||
* Permissions for future use?
|
||||
* Maybe...
|
||||
*/
|
||||
// add "view meal" permission
|
||||
$viewMeal = $auth->createPermission('viewMeal');
|
||||
$viewMeal->description = 'View own meal records';
|
||||
$auth->add($viewMeal);
|
||||
|
||||
// add "create meal" permission
|
||||
$createMeal = $auth->createPermission('createMeal');
|
||||
$createMeal->description = 'Create a new meal record';
|
||||
$auth->add($createMeal);
|
||||
|
||||
// add "update meal" permission
|
||||
$updateMeal = $auth->createPermission('updateMeal');
|
||||
$updateMeal->description = 'Update own meal records';
|
||||
$auth->add($updateMeal);
|
||||
|
||||
// add "delete meal" permission
|
||||
$deleteMeal = $auth->createPermission('deleteMeal');
|
||||
$deleteMeal->description = 'Delete own meal records';
|
||||
$auth->add($deleteMeal);
|
||||
|
||||
|
||||
// add "view all meals" permission (for admin)
|
||||
$viewAllMeals = $auth->createPermission('viewAllMeals');
|
||||
$viewAllMeals->description = 'View all meal records';
|
||||
$auth->add($viewAllMeals);
|
||||
|
||||
// add "update all meals" permission (for admin)
|
||||
$updateAllMeals = $auth->createPermission('updateAllMeals');
|
||||
$updateAllMeals->description = 'Update any meal record';
|
||||
$auth->add($updateAllMeals);
|
||||
|
||||
// add "delete all meals" permission (for admin)
|
||||
$deleteAllMeals = $auth->createPermission('deleteAllMeals');
|
||||
$deleteAllMeals->description = 'Delete any meal record';
|
||||
$auth->add($deleteAllMeals);
|
||||
|
||||
// Add roles
|
||||
$user = $auth->createRole('user');
|
||||
$auth->add($user);
|
||||
|
||||
$admin = $auth->createRole('admin');
|
||||
$auth->add($admin);
|
||||
|
||||
|
||||
// Add permissions to roles (crucially important):
|
||||
$auth->addChild($user, $viewMeal);
|
||||
$auth->addChild($user, $createMeal);
|
||||
$auth->addChild($user, $updateMeal);
|
||||
$auth->addChild($user, $deleteMeal);
|
||||
|
||||
$auth->addChild($admin, $viewMeal);
|
||||
$auth->addChild($admin, $createMeal);
|
||||
$auth->addChild($admin, $updateMeal);
|
||||
$auth->addChild($admin, $deleteMeal);
|
||||
$auth->addChild($admin, $viewAllMeals);
|
||||
$auth->addChild($admin, $updateAllMeals);
|
||||
$auth->addChild($admin, $deleteAllMeals);
|
||||
|
||||
// Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
|
||||
// usually implemented in your User model.
|
||||
$auth->assign($admin, 1);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$auth = Yii::$app->authManager;
|
||||
|
||||
$auth->removeAll();
|
||||
}
|
||||
}
|
||||
47
console/migrations/m230210_155341_queue_table.php
Normal file
47
console/migrations/m230210_155341_queue_table.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use yii\db\Migration;
|
||||
|
||||
/**
|
||||
* Class m230210_155341_queue_table
|
||||
*/
|
||||
class m230210_155341_queue_table extends Migration
|
||||
{
|
||||
public $tableName = '{{%queue}}';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeUp()
|
||||
{
|
||||
$tableOptions = null;
|
||||
if ($this->db->driverName === 'mysql') {
|
||||
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
|
||||
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
|
||||
}
|
||||
|
||||
$this->createTable($this->tableName, [
|
||||
'id' => $this->primaryKey(),
|
||||
'channel' => $this->string()->notNull(),
|
||||
'job' => $this->binary()->notNull(),
|
||||
'pushed_at' => $this->integer()->notNull(),
|
||||
'ttr' => $this->integer()->notNull(),
|
||||
'delay' => $this->integer()->notNull(),
|
||||
'priority' => $this->integer()->unsigned()->notNull()->defaultValue(1024),
|
||||
'reserved_at' => $this->integer(),
|
||||
'attempt' => $this->integer(),
|
||||
'done_at' => $this->integer(),
|
||||
], $tableOptions);
|
||||
$this->createIndex('channel', $this->tableName, 'channel');
|
||||
$this->createIndex('reserved_at', $this->tableName, 'reserved_at');
|
||||
$this->createIndex('priority', $this->tableName, 'priority');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeDown()
|
||||
{
|
||||
$this->dropTable($this->tableName);
|
||||
}
|
||||
|
||||
}
|
||||
36
console/migrations/m250219_133939_create_meal_table.php
Normal file
36
console/migrations/m250219_133939_create_meal_table.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use yii\db\Migration;
|
||||
|
||||
/**
|
||||
* Handles the creation of table `{{%meal}}`.
|
||||
*/
|
||||
class m250219_133939_create_meal_table extends Migration
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeUp()
|
||||
{
|
||||
$this->createTable('{{%meal}}', [
|
||||
'id' => $this->primaryKey(),
|
||||
'file_name' => $this->string()->notNull(),
|
||||
'calories' => $this->integer()->notNull(),
|
||||
'protein' => $this->integer()->notNull(),
|
||||
'fat' => $this->integer()->notNull(),
|
||||
'carbohydrates' => $this->integer()->notNull(),
|
||||
'fiber' => $this->integer()->notNull(),
|
||||
'meal' => $this->integer()->notNull(),
|
||||
'created_at' => $this->integer()->notNull(),
|
||||
'updated_at' => $this->integer()->notNull(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeDown()
|
||||
{
|
||||
$this->dropTable('{{%meal}}');
|
||||
}
|
||||
}
|
||||
1
console/models/.gitkeep
Normal file
1
console/models/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
2
console/runtime/.gitignore
vendored
Normal file
2
console/runtime/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
54
deploy.php
Normal file
54
deploy.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Deployer;
|
||||
|
||||
require 'recipe/yii.php';
|
||||
|
||||
// Config
|
||||
set('repository', 'git@github.com:cgsmith/calorie.git');
|
||||
|
||||
add('shared_files', [
|
||||
//'yii',
|
||||
'common/config/main-local.php',
|
||||
'common/config/params-local.php',
|
||||
'frontend/config/main-local.php',
|
||||
'frontend/config/params-local.php',
|
||||
]);
|
||||
add('shared_dirs', []);
|
||||
add('writable_dirs', []);
|
||||
|
||||
// Hosts
|
||||
host('calorie')
|
||||
->set('remote_user', 'root')
|
||||
->set('deploy_path', '/var/www/calorie')
|
||||
->set('environment', 'Production')
|
||||
->setLabels([
|
||||
'env' => 'prod',
|
||||
]);
|
||||
host('test.calorie')
|
||||
->set('composer_options', '--verbose --prefer-dist --no-progress --no-interaction')
|
||||
->set('remote_user', 'root')
|
||||
->set('deploy_path', '/var/www/test.calorie')
|
||||
->set('environment', 'Testing')
|
||||
->setLabels([
|
||||
'env' => 'test',
|
||||
]);
|
||||
|
||||
// Tasks
|
||||
task('init-app', function () {
|
||||
run('cd {{release_or_current_path}} && {{bin/php}} init --env={{environment}} --overwrite=n');
|
||||
});
|
||||
|
||||
desc('Restart yii queue workers');
|
||||
task('yii:queue:restart', function () {
|
||||
run('systemctl restart yii-queue@*');
|
||||
});
|
||||
|
||||
task('deploy:prod', function() {
|
||||
invoke('yii:queue:restart');
|
||||
})->select('env=prod');
|
||||
|
||||
|
||||
// Hooks
|
||||
after('deploy:vendors', 'init-app');
|
||||
after('deploy:failed', 'deploy:unlock');
|
||||
after('deploy', 'deploy:prod');
|
||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
|
||||
frontend:
|
||||
build: frontend
|
||||
ports:
|
||||
- "20080:80"
|
||||
environment:
|
||||
- PHP_ENABLE_XDEBUG=1
|
||||
volumes:
|
||||
# Re-use local composer cache via host-volume
|
||||
- ~/.composer-docker/cache:/root/.composer/cache:delegated
|
||||
# Mount source-code for development
|
||||
- ./:/app
|
||||
extra_hosts: # https://stackoverflow.com/a/67158212/1106908
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
mysql:
|
||||
image: mysql:8
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123
|
||||
MYSQL_DATABASE: app
|
||||
MYSQL_USER: app
|
||||
MYSQL_PASSWORD: 123
|
||||
ports:
|
||||
- "20083:3306"
|
||||
volumes:
|
||||
- ./mysql-data/var/lib/mysql:/var/lib/mysql
|
||||
16
environments/dev/common/config/codeception-local.php
Normal file
16
environments/dev/common/config/codeception-local.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return yii\helpers\ArrayHelper::merge(
|
||||
require __DIR__ . '/main.php',
|
||||
require __DIR__ . '/main-local.php',
|
||||
require __DIR__ . '/test.php',
|
||||
require __DIR__ . '/test-local.php',
|
||||
[
|
||||
'components' => [
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => '',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
23
environments/dev/common/config/main-local.php
Normal file
23
environments/dev/common/config/main-local.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'components' => [
|
||||
'db' => [
|
||||
'class' => \yii\db\Connection::class,
|
||||
'dsn' => 'mysql:host=mysql;dbname=app',
|
||||
'username' => 'app',
|
||||
'password' => '123',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'queue' => [
|
||||
'class' => \yii\queue\sync\Queue::class,
|
||||
'handle' => true, // whether tasks should be executed immediately
|
||||
],
|
||||
'mailer' => [
|
||||
'class' => \yii\symfonymailer\Mailer::class,
|
||||
'viewPath' => '@common/mail',
|
||||
// send all mails to a file by default.
|
||||
'useFileTransport' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
8
environments/dev/common/config/params-local.php
Normal file
8
environments/dev/common/config/params-local.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'company_name' => 'Five Devs',
|
||||
'product_name' => 'Calorie',
|
||||
'sonar.url' => 'https://yourname.sonar.software', # no trailing slash
|
||||
'sonar.bearerToken' => '',
|
||||
];
|
||||
9
environments/dev/common/config/test-local.php
Normal file
9
environments/dev/common/config/test-local.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'components' => [
|
||||
'db' => [
|
||||
'dsn' => 'mysql:host=localhost;dbname=yii2advanced_test',
|
||||
],
|
||||
],
|
||||
];
|
||||
8
environments/dev/console/config/main-local.php
Normal file
8
environments/dev/console/config/main-local.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'bootstrap' => ['gii'],
|
||||
'modules' => [
|
||||
'gii' => 'yii\gii\Module',
|
||||
],
|
||||
];
|
||||
4
environments/dev/console/config/params-local.php
Normal file
4
environments/dev/console/config/params-local.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
4
environments/dev/console/config/test-local.php
Normal file
4
environments/dev/console/config/test-local.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
11
environments/dev/frontend/config/codeception-local.php
Normal file
11
environments/dev/frontend/config/codeception-local.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return yii\helpers\ArrayHelper::merge(
|
||||
require dirname(dirname(__DIR__)) . '/common/config/codeception-local.php',
|
||||
require __DIR__ . '/main.php',
|
||||
require __DIR__ . '/main-local.php',
|
||||
require __DIR__ . '/test.php',
|
||||
require __DIR__ . '/test-local.php',
|
||||
[
|
||||
]
|
||||
);
|
||||
27
environments/dev/frontend/config/main-local.php
Normal file
27
environments/dev/frontend/config/main-local.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
$config = [
|
||||
'components' => [
|
||||
'request' => [
|
||||
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||
'cookieValidationKey' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (!YII_ENV_TEST) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
$config['bootstrap'][] = 'debug';
|
||||
$config['modules']['debug'] = [
|
||||
'class' => \yii\debug\Module::class,
|
||||
'allowedIPs' => ['*'],
|
||||
];
|
||||
|
||||
$config['bootstrap'][] = 'gii';
|
||||
$config['modules']['gii'] = [
|
||||
'class' => \yii\gii\Module::class,
|
||||
'allowedIPs' => ['*'],
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
4
environments/dev/frontend/config/params-local.php
Normal file
4
environments/dev/frontend/config/params-local.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
4
environments/dev/frontend/config/test-local.php
Normal file
4
environments/dev/frontend/config/test-local.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
];
|
||||
28
environments/dev/frontend/web/index-test.php
Normal file
28
environments/dev/frontend/web/index-test.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
// NOTE: Make sure this file is not accessible when deployed to production
|
||||
if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
|
||||
die('You are not allowed to access this file.');
|
||||
}
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||
require __DIR__ . '/../../common/config/bootstrap.php';
|
||||
require __DIR__ . '/../config/bootstrap.php';
|
||||
|
||||
|
||||
$config = yii\helpers\ArrayHelper::merge(
|
||||
require __DIR__ . '/../../common/config/main.php',
|
||||
require __DIR__ . '/../../common/config/main-local.php',
|
||||
require __DIR__ . '/../../common/config/test.php',
|
||||
require __DIR__ . '/../../common/config/test-local.php',
|
||||
require __DIR__ . '/../config/main.php',
|
||||
require __DIR__ . '/../config/main-local.php',
|
||||
require __DIR__ . '/../config/test.php',
|
||||
require __DIR__ . '/../config/test-local.php'
|
||||
);
|
||||
|
||||
(new yii\web\Application($config))->run();
|
||||
18
environments/dev/frontend/web/index.php
Normal file
18
environments/dev/frontend/web/index.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||
defined('YII_ENV') or define('YII_ENV', 'dev');
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||
require __DIR__ . '/../../common/config/bootstrap.php';
|
||||
require __DIR__ . '/../config/bootstrap.php';
|
||||
|
||||
$config = yii\helpers\ArrayHelper::merge(
|
||||
require __DIR__ . '/../../common/config/main.php',
|
||||
require __DIR__ . '/../../common/config/main-local.php',
|
||||
require __DIR__ . '/../config/main.php',
|
||||
require __DIR__ . '/../config/main-local.php'
|
||||
);
|
||||
|
||||
(new yii\web\Application($config))->run();
|
||||
2
environments/dev/frontend/web/robots.txt
Normal file
2
environments/dev/frontend/web/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user